Skip to content

Fix various problems with top-level 'guard' statements and captures #28596

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 7 commits into from
Dec 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 18 additions & 2 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ class alignas(1 << DeclAlignInBits) Decl {
HasSingleExpressionBody : 1
);

SWIFT_INLINE_BITFIELD(FuncDecl, AbstractFunctionDecl, 1+1+2+1+1+2,
SWIFT_INLINE_BITFIELD(FuncDecl, AbstractFunctionDecl, 1+1+2+1+1+2+1,
/// Whether we've computed the 'static' flag yet.
IsStaticComputed : 1,

Expand All @@ -416,7 +416,12 @@ class alignas(1 << DeclAlignInBits) Decl {
SelfAccessComputed : 1,

/// Backing bits for 'self' access kind.
SelfAccess : 2
SelfAccess : 2,

/// Whether this is a top-level function which should be treated
/// as if it were in local context for the purposes of capture
/// analysis.
HasTopLevelLocalContextCaptures : 1
);

SWIFT_INLINE_BITFIELD(AccessorDecl, FuncDecl, 4+1+1,
Expand Down Expand Up @@ -2708,6 +2713,10 @@ class ValueDecl : public Decl {
AccessSemantics getAccessSemanticsFromContext(const DeclContext *DC,
bool isAccessOnSelf) const;

/// Determines if a reference to this declaration from a nested function
/// should be treated like a capture of a local value.
bool isLocalCapture() const;

/// Print a reference to the given declaration.
std::string printRef() const;

Expand Down Expand Up @@ -6078,6 +6087,7 @@ class FuncDecl : public AbstractFunctionDecl {
Bits.FuncDecl.SelfAccessComputed = false;
Bits.FuncDecl.IsStaticComputed = false;
Bits.FuncDecl.IsStatic = false;
Bits.FuncDecl.HasTopLevelLocalContextCaptures = false;
}

private:
Expand Down Expand Up @@ -6235,6 +6245,12 @@ class FuncDecl : public AbstractFunctionDecl {
/// Perform basic checking to determine whether the @IBAction or
/// @IBSegueAction attribute can be applied to this function.
bool isPotentialIBActionTarget() const;

bool hasTopLevelLocalContextCaptures() const {
return Bits.FuncDecl.HasTopLevelLocalContextCaptures;
}

void setHasTopLevelLocalContextCaptures(bool hasCaptures=true);
};

/// This represents an accessor function, such as a getter or setter.
Expand Down
12 changes: 10 additions & 2 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -856,11 +856,19 @@ class Parser {
void consumeTopLevelDecl(ParserPosition BeginParserPosition,
TopLevelCodeDecl *TLCD);

ParserStatus parseBraceItems(SmallVectorImpl<ASTNode> &Decls,
BraceItemListKind Kind,
BraceItemListKind ConditionalBlockKind,
bool &IsFollowingGuard);
ParserStatus parseBraceItems(SmallVectorImpl<ASTNode> &Decls,
BraceItemListKind Kind =
BraceItemListKind::Brace,
BraceItemListKind ConditionalBlockKind =
BraceItemListKind::Brace);
BraceItemListKind::Brace) {
bool IsFollowingGuard = false;
return parseBraceItems(Decls, Kind, ConditionalBlockKind,
IsFollowingGuard);
}
ParserResult<BraceStmt> parseBraceItemList(Diag<> ID);

//===--------------------------------------------------------------------===//
Expand All @@ -869,7 +877,7 @@ class Parser {
/// Return true if parser is at the start of a decl or decl-import.
bool isStartOfDecl();

bool parseTopLevel();
void parseTopLevel();

/// Flags that control the parsing of declarations.
enum ParseDeclFlags {
Expand Down
18 changes: 0 additions & 18 deletions include/swift/SIL/TypeLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -946,24 +946,6 @@ class TypeConverter {
CaptureInfo getLoweredLocalCaptures(SILDeclRef fn);
bool hasLoweredLocalCaptures(SILDeclRef fn);

#ifndef NDEBUG
/// If \c false, \c childDC is in a context it cannot capture variables from,
/// so it is expected that Sema may not have computed its \c CaptureInfo.
///
/// This call exists for use in assertions; do not use it to skip capture
/// processing.
static bool canCaptureFromParent(DeclContext *childDC) {
// This call was added because Sema leaves the captures of functions that
// cannot capture anything uncomputed.
// TODO: Make Sema set them to CaptureInfo::empty() instead.

if (childDC)
if (auto decl = childDC->getAsDecl())
return decl->getDeclContext()->isLocalContext();
return true;
}
#endif

enum class ABIDifference : uint8_t {
// Types have compatible calling conventions and representations, so can
// be trivially bitcast.
Expand Down
8 changes: 2 additions & 6 deletions include/swift/Subsystems.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,18 +119,14 @@ namespace swift {
///
/// \param PersistentState if non-null the same PersistentState object can
/// be used to resume parsing or parse delayed function bodies.
///
/// \return true if the parser found code with side effects.
bool parseIntoSourceFile(SourceFile &SF, unsigned BufferID, bool *Done,
void parseIntoSourceFile(SourceFile &SF, unsigned BufferID, bool *Done,
SILParserState *SIL = nullptr,
PersistentParserState *PersistentState = nullptr,
bool DelayBodyParsing = true);

/// Parse a single buffer into the given source file, until the full source
/// contents are parsed.
///
/// \return true if the parser found code with side effects.
bool parseIntoSourceFileFull(SourceFile &SF, unsigned BufferID,
void parseIntoSourceFileFull(SourceFile &SF, unsigned BufferID,
PersistentParserState *PersistentState = nullptr,
bool DelayBodyParsing = true);

Expand Down
4 changes: 2 additions & 2 deletions lib/AST/CaptureInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ CaptureInfo CaptureInfo::empty() {

bool CaptureInfo::hasLocalCaptures() const {
for (auto capture : getCaptures())
if (capture.getDecl()->getDeclContext()->isLocalContext())
if (capture.getDecl()->isLocalCapture())
return true;
return false;
}
Expand All @@ -71,7 +71,7 @@ getLocalCaptures(SmallVectorImpl<CapturedValue> &Result) const {

// Filter out global variables.
for (auto capture : getCaptures()) {
if (!capture.getDecl()->getDeclContext()->isLocalContext())
if (!capture.getDecl()->isLocalCapture())
continue;

Result.push_back(capture);
Expand Down
16 changes: 16 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2866,6 +2866,16 @@ bool ValueDecl::isImplicitlyUnwrappedOptional() const {
false);
}

bool ValueDecl::isLocalCapture() const {
auto *dc = getDeclContext();

if (auto *fd = dyn_cast<FuncDecl>(this))
if (isa<SourceFile>(dc))
return fd->hasTopLevelLocalContextCaptures();

return dc->isLocalContext();
}

ArrayRef<ValueDecl *>
ValueDecl::getSatisfiedProtocolRequirements(bool Sorted) const {
// Dig out the nominal type.
Expand Down Expand Up @@ -7669,6 +7679,12 @@ bool FuncDecl::isPotentialIBActionTarget() const {
!isa<AccessorDecl>(this);
}

void FuncDecl::setHasTopLevelLocalContextCaptures(bool hasCaptures) {
assert(!hasCaptures || isa<SourceFile>(getDeclContext()));

Bits.FuncDecl.HasTopLevelLocalContextCaptures = hasCaptures;
}

Type TypeBase::getSwiftNewtypeUnderlyingType() {
auto structDecl = getStructOrBoundGenericStruct();
if (!structDecl)
Expand Down
6 changes: 2 additions & 4 deletions lib/Immediate/REPL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,10 @@ typeCheckREPLInput(ModuleDecl *MostRecentModule, StringRef Name,
REPLInputFile.addImports(ImportsWithOptions);
}

bool FoundAnySideEffects = false;
bool Done;
do {
FoundAnySideEffects |=
parseIntoSourceFile(REPLInputFile, BufferID, &Done, nullptr,
&PersistentState);
parseIntoSourceFile(REPLInputFile, BufferID, &Done, nullptr,
&PersistentState);
} while (!Done);
performTypeChecking(REPLInputFile);
return REPLModule;
Expand Down
15 changes: 1 addition & 14 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ namespace {
/// decl-sil [[only in SIL mode]
/// decl-sil-stage [[only in SIL mode]
/// \endverbatim
bool Parser::parseTopLevel() {
void Parser::parseTopLevel() {
SF.ASTStage = SourceFile::Parsing;

// Prime the lexer.
Expand Down Expand Up @@ -246,17 +246,6 @@ bool Parser::parseTopLevel() {
consumeToken();
}

// If this is a Main source file, determine if we found code that needs to be
// executed (this is used by the repl to know whether to compile and run the
// newly parsed stuff).
bool FoundTopLevelCodeToExecute = false;
if (allowTopLevelCode()) {
for (auto V : Items) {
if (isa<TopLevelCodeDecl>(V.get<Decl*>()))
FoundTopLevelCodeToExecute = true;
}
}

// Add newly parsed decls to the module.
for (auto Item : Items) {
if (auto *D = Item.dyn_cast<Decl*>()) {
Expand All @@ -279,8 +268,6 @@ bool Parser::parseTopLevel() {
SyntaxContext->addToken(Tok, LeadingTrivia, TrailingTrivia);
TokReceiver->finalize();
}

return FoundTopLevelCodeToExecute;
}

ParserResult<AvailableAttr> Parser::parseExtendedAvailabilitySpecList(
Expand Down
49 changes: 26 additions & 23 deletions lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ bool Parser::isTerminatorForBraceItemListKind(BraceItemListKind Kind,
ArrayRef<ASTNode> ParsedDecls) {
switch (Kind) {
case BraceItemListKind::Brace:
case BraceItemListKind::TopLevelCode:
case BraceItemListKind::TopLevelLibrary:
return false;
case BraceItemListKind::Case: {
if (Tok.is(tok::pound_if)) {
Expand All @@ -220,26 +222,6 @@ bool Parser::isTerminatorForBraceItemListKind(BraceItemListKind Kind,
}
return isAtStartOfSwitchCase(*this);
}
case BraceItemListKind::TopLevelCode:
// When parsing the top level executable code for a module, if we parsed
// some executable code, then we're done. We want to process (name bind,
// type check, etc) decls one at a time to make sure that there are not
// forward type references, etc. There is an outer loop around the parser
// that will reinvoke the parser at the top level on each statement until
// EOF. In contrast, it is ok to have forward references between classes,
// functions, etc.
for (auto I : ParsedDecls) {
if (isa<TopLevelCodeDecl>(I.get<Decl*>()))
// Only bail out if the next token is at the start of a line. If we
// don't, then we may accidentally allow things like "a = 1 b = 4".
// FIXME: This is really dubious. This will reject some things, but
// allow other things we don't want.
if (Tok.isAtStartOfLine())
return true;
}
return false;
case BraceItemListKind::TopLevelLibrary:
return false;
case BraceItemListKind::ActiveConditionalBlock:
case BraceItemListKind::InactiveConditionalBlock:
return Tok.isNot(tok::pound_else) && Tok.isNot(tok::pound_endif) &&
Expand Down Expand Up @@ -289,7 +271,8 @@ void Parser::consumeTopLevelDecl(ParserPosition BeginParserPosition,
/// expr '=' expr
ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
BraceItemListKind Kind,
BraceItemListKind ConditionalBlockKind) {
BraceItemListKind ConditionalBlockKind,
bool &IsFollowingGuard) {
bool isRootCtx = SyntaxContext->isRoot();
SyntaxParsingContext ItemListContext(SyntaxContext,
SyntaxKind::CodeBlockItemList);
Expand Down Expand Up @@ -382,7 +365,8 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
parseBraceItems(Elements, Kind, IsActive
? BraceItemListKind::ActiveConditionalBlock
: BraceItemListKind::InactiveConditionalBlock);
: BraceItemListKind::InactiveConditionalBlock,
IsFollowingGuard);
});
if (IfConfigResult.hasCodeCompletion() && isCodeCompletionFirstPass()) {
consumeDecl(BeginParserPosition, None, IsTopLevel);
Expand Down Expand Up @@ -419,7 +403,18 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
ParserResult<Decl> DeclResult =
parseDecl(IsTopLevel ? PD_AllowTopLevel : PD_Default,
IsAtStartOfLineOrPreviousHadSemi,
[&](Decl *D) {TmpDecls.push_back(D);});
[&](Decl *D) {
TmpDecls.push_back(D);

// Any function after a 'guard' statement is marked as
// possibly having local captures. This allows SILGen
// to correctly determine its capture list, since
// otherwise it would be skipped because it is not
// defined inside a local context.
if (IsFollowingGuard)
if (auto *FD = dyn_cast<FuncDecl>(D))
FD->setHasTopLevelLocalContextCaptures();
});
BraceItemsStatus |= DeclResult;
if (DeclResult.isParseError()) {
NeedParseErrorRecovery = true;
Expand Down Expand Up @@ -469,6 +464,14 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
Result, Result.getEndLoc());
TLCD->setBody(Brace);
Entries.push_back(TLCD);

// A top-level 'guard' statement can introduce local bindings, so we
// must mark all functions following one. This makes them behave
// as if they were in local context for the purposes of capture
// emission in SILGen.
if (auto *stmt = Result.dyn_cast<Stmt *>())
if (isa<GuardStmt>(stmt))
IsFollowingGuard = true;
}
} else if (Tok.is(tok::kw_init) && isa<ConstructorDecl>(CurDeclContext)) {
SourceLoc StartLoc = Tok.getLoc();
Expand Down
38 changes: 17 additions & 21 deletions lib/ParseSIL/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ void PrettyStackTraceParser::print(llvm::raw_ostream &out) const {
out << '\n';
}

static bool parseIntoSourceFileImpl(SourceFile &SF,
unsigned BufferID,
bool *Done,
SILParserState *SIL,
PersistentParserState *PersistentState,
bool FullParse,
bool DelayBodyParsing) {
static void parseIntoSourceFileImpl(SourceFile &SF,
unsigned BufferID,
bool *Done,
SILParserState *SIL,
PersistentParserState *PersistentState,
bool FullParse,
bool DelayBodyParsing) {
assert((!FullParse || (SF.canBeParsedInFull() && !SIL)) &&
"cannot parse in full with the given parameters!");

Expand Down Expand Up @@ -149,40 +149,36 @@ static bool parseIntoSourceFileImpl(SourceFile &SF,
llvm::SaveAndRestore<bool> S(P.IsParsingInterfaceTokens,
SF.hasInterfaceHash());

bool FoundSideEffects = false;
do {
bool hasSideEffects = P.parseTopLevel();
FoundSideEffects = FoundSideEffects || hasSideEffects;
P.parseTopLevel();
*Done = P.Tok.is(tok::eof);
} while (FullParse && !*Done);

if (STreeCreator) {
auto rawNode = P.finalizeSyntaxTree();
STreeCreator->acceptSyntaxRoot(rawNode, SF);
}

return FoundSideEffects;
}

bool swift::parseIntoSourceFile(SourceFile &SF,
void swift::parseIntoSourceFile(SourceFile &SF,
unsigned BufferID,
bool *Done,
SILParserState *SIL,
PersistentParserState *PersistentState,
bool DelayBodyParsing) {
return parseIntoSourceFileImpl(SF, BufferID, Done, SIL,
PersistentState,
/*FullParse=*/SF.shouldBuildSyntaxTree(),
DelayBodyParsing);
parseIntoSourceFileImpl(SF, BufferID, Done, SIL,
PersistentState,
/*FullParse=*/SF.shouldBuildSyntaxTree(),
DelayBodyParsing);
}

bool swift::parseIntoSourceFileFull(SourceFile &SF, unsigned BufferID,
void swift::parseIntoSourceFileFull(SourceFile &SF, unsigned BufferID,
PersistentParserState *PersistentState,
bool DelayBodyParsing) {
bool Done = false;
return parseIntoSourceFileImpl(SF, BufferID, &Done, /*SIL=*/nullptr,
PersistentState, /*FullParse=*/true,
DelayBodyParsing);
parseIntoSourceFileImpl(SF, BufferID, &Done, /*SIL=*/nullptr,
PersistentState, /*FullParse=*/true,
DelayBodyParsing);
}


Expand Down
Loading