Skip to content

[5.0][Parse] Eliminate backtracking in collection expression parsing #21674

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
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
3 changes: 1 addition & 2 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1333,8 +1333,7 @@ class Parser {
ParserResult<Expr> parseExprCallSuffix(ParserResult<Expr> fn,
bool isExprBasic);
ParserResult<Expr> parseExprCollection();
ParserResult<Expr> parseExprArray(SourceLoc LSquareLoc);
ParserResult<Expr> parseExprDictionary(SourceLoc LSquareLoc);
ParserResult<Expr> parseExprCollectionElement(Optional<bool> &isDictionary);
ParserResult<Expr> parseExprPoundAssert();
ParserResult<Expr> parseExprPoundUnknown(SourceLoc LSquareLoc);
ParserResult<Expr>
Expand Down
238 changes: 112 additions & 126 deletions lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3343,10 +3343,17 @@ Parser::parseExprCallSuffix(ParserResult<Expr> fn, bool isExprBasic) {
/// expr-collection:
/// expr-array
/// expr-dictionary
// lsquare-starting ']'
/// expr-array:
/// '[' expr (',' expr)* ','? ']'
/// '[' ']'
/// expr-dictionary:
/// '[' expr ':' expr (',' expr ':' expr)* ','? ']'
/// '[' ':' ']'
ParserResult<Expr> Parser::parseExprCollection() {
SyntaxParsingContext ArrayOrDictContext(SyntaxContext);
SyntaxParsingContext ArrayOrDictContext(SyntaxContext,
SyntaxContextKind::Expr);
SourceLoc LSquareLoc = consumeToken(tok::l_square);
SourceLoc RSquareLoc;

Parser::StructureMarkerRAII ParsingCollection(
*this, LSquareLoc,
Expand All @@ -3357,7 +3364,7 @@ ParserResult<Expr> Parser::parseExprCollection() {
if (SyntaxContext->isEnabled())
SyntaxContext->addSyntax(
SyntaxFactory::makeBlankArrayElementList(Context.getSyntaxArena()));
SourceLoc RSquareLoc = consumeToken(tok::r_square);
RSquareLoc = consumeToken(tok::r_square);
ArrayOrDictContext.setCreateSyntax(SyntaxKind::ArrayExpr);
return makeParserResult(
ArrayExpr::create(Context, LSquareLoc, {}, {}, RSquareLoc));
Expand All @@ -3366,7 +3373,7 @@ ParserResult<Expr> Parser::parseExprCollection() {
// [:] is always an empty dictionary.
if (Tok.is(tok::colon) && peekToken().is(tok::r_square)) {
consumeToken(tok::colon);
SourceLoc RSquareLoc = consumeToken(tok::r_square);
RSquareLoc = consumeToken(tok::r_square);
ArrayOrDictContext.setCreateSyntax(SyntaxKind::DictionaryExpr);
return makeParserResult(
DictionaryExpr::create(Context, LSquareLoc, {}, {}, RSquareLoc));
Expand All @@ -3381,147 +3388,126 @@ ParserResult<Expr> Parser::parseExprCollection() {
return parseExprPoundUnknown(LSquareLoc);
}

bool ParseDict;
ParserStatus Status;
Optional<bool> isDictionary;
SmallVector<Expr *, 8> ElementExprs;
SmallVector<SourceLoc, 8> CommaLocs;

{
BacktrackingScope Scope(*this);
auto HasDelayedDecl = State->hasDelayedDecl();
// Parse the first expression.
ParserResult<Expr> FirstExpr
= parseExpr(diag::expected_expr_in_collection_literal);
if (FirstExpr.isNull() || Tok.is(tok::eof)) {
skipUntil(tok::r_square);
if (Tok.is(tok::r_square))
consumeToken();
Scope.cancelBacktrack();
ArrayOrDictContext.setCoerceKind(SyntaxContextKind::Expr);
return FirstExpr;
}
if (!HasDelayedDecl)
State->takeDelayedDeclState();
// If we have a ':', this is a dictionary literal.
ParseDict = Tok.is(tok::colon);
}
SyntaxParsingContext ListCtx(SyntaxContext, SyntaxContextKind::Expr);

while (true) {
SyntaxParsingContext ElementCtx(SyntaxContext);

auto Element = parseExprCollectionElement(isDictionary);
Status |= Element;
ElementCtx.setCreateSyntax(*isDictionary ? SyntaxKind::DictionaryElement
: SyntaxKind::ArrayElement);
if (Element.isNonNull())
ElementExprs.push_back(Element.get());

// Skip to ']' or ',' in case of error.
// NOTE: This checks 'Status' instead of 'Element' to silence excessive
// diagnostics.
if (Status.isError()) {
skipUntilDeclRBrace(tok::r_square, tok::comma);
if (Tok.isNot(tok::comma))
break;
}

if (ParseDict) {
ArrayOrDictContext.setCreateSyntax(SyntaxKind::DictionaryExpr);
return parseExprDictionary(LSquareLoc);
} else {
ArrayOrDictContext.setCreateSyntax(SyntaxKind::ArrayExpr);
return parseExprArray(LSquareLoc);
}
}
// Parse the ',' if exists.
if (Tok.is(tok::comma)) {
CommaLocs.push_back(consumeToken());
if (!Tok.is(tok::r_square))
continue;
}

/// parseExprArray - Parse an array literal expression.
///
/// The lsquare-starting and first expression have already been
/// parsed, and are passed in as parameters.
///
/// expr-array:
/// '[' expr (',' expr)* ','? ']'
/// '[' ']'
ParserResult<Expr> Parser::parseExprArray(SourceLoc LSquareLoc) {
SmallVector<Expr *, 8> SubExprs;
SmallVector<SourceLoc, 8> CommaLocs;
// Close square.
if (Tok.is(tok::r_square))
break;

SourceLoc RSquareLoc;
ParserStatus Status;
bool First = true;
bool HasError = false;
Status |= parseList(tok::r_square, LSquareLoc, RSquareLoc,
/*AllowSepAfterLast=*/true,
diag::expected_rsquare_array_expr,
SyntaxKind::ArrayElementList,
[&] () -> ParserStatus
{
ParserResult<Expr> Element
= parseExpr(diag::expected_expr_in_collection_literal);
if (Element.isNonNull())
SubExprs.push_back(Element.get());

if (First) {
if (Tok.isNot(tok::r_square) && Tok.isNot(tok::comma)) {
diagnose(Tok, diag::expected_separator, ",").
fixItInsertAfter(PreviousLoc, ",");
HasError = true;
// If we found EOF or such, bailout.
if (Tok.is(tok::eof)) {
IsInputIncomplete = true;
break;
}
First = false;

// If The next token is at the beginning of a new line and can never start
// an element, break.
if (Tok.isAtStartOfLine() && (Tok.isAny(tok::r_brace, tok::pound_endif) ||
isStartOfDecl() || isStartOfStmt()))
break;

diagnose(Tok, diag::expected_separator, ",")
.fixItInsertAfter(PreviousLoc, ",");
Status.setIsParseError();
}

if (Tok.is(tok::comma))
CommaLocs.push_back(Tok.getLoc());
return Element;
});
if (HasError)
ListCtx.setCreateSyntax(*isDictionary ? SyntaxKind::DictionaryElementList
: SyntaxKind::ArrayElementList);
}
ArrayOrDictContext.setCreateSyntax(*isDictionary ? SyntaxKind::DictionaryExpr
: SyntaxKind::ArrayExpr);

if (Status.isError()) {
// If we've already got errors, don't emit missing RightK diagnostics.
RSquareLoc = Tok.is(tok::r_square) ? consumeToken() : PreviousLoc;
} else if (parseMatchingToken(tok::r_square, RSquareLoc,
diag::expected_rsquare_array_expr,
LSquareLoc)) {
Status.setIsParseError();
}

assert(SubExprs.size() >= 1);
return makeParserResult(Status,
ArrayExpr::create(Context, LSquareLoc, SubExprs, CommaLocs,
RSquareLoc));
// Don't bother to create expression if any expressions aren't parsed.
if (ElementExprs.empty())
return Status;

Expr *expr;
if (*isDictionary)
expr = DictionaryExpr::create(Context, LSquareLoc, ElementExprs, CommaLocs,
RSquareLoc);
else
expr = ArrayExpr::create(Context, LSquareLoc, ElementExprs, CommaLocs,
RSquareLoc);

return makeParserResult(Status, expr);
}

/// parseExprDictionary - Parse a dictionary literal expression.
///
/// The lsquare-starting and first key have already been parsed, and
/// are passed in as parameters.
/// parseExprCollectionElement - Parse an element for collection expr.
///
/// expr-dictionary:
/// '[' expr ':' expr (',' expr ':' expr)* ','? ']'
/// '[' ':' ']'
ParserResult<Expr> Parser::parseExprDictionary(SourceLoc LSquareLoc) {
// Each subexpression is a (key, value) tuple.
// FIXME: We're not tracking the colon locations in the AST.
SmallVector<Expr *, 8> SubExprs;
SmallVector<SourceLoc, 8> CommaLocs;
SourceLoc RSquareLoc;

// Function that adds a new key/value pair.
auto addKeyValuePair = [&](Expr *Key, Expr *Value) -> void {
Expr *Exprs[] = {Key, Value};
SubExprs.push_back(TupleExpr::createImplicit(Context, Exprs, { }));
};
/// If \p isDictionary is \c None, it's set to \c true if the element is for
/// dictionary literal, or \c false otherwise.
ParserResult<Expr>
Parser::parseExprCollectionElement(Optional<bool> &isDictionary) {
auto Element = parseExpr(isDictionary.hasValue() && *isDictionary
? diag::expected_key_in_dictionary_literal
: diag::expected_expr_in_collection_literal);

ParserStatus Status;
Status |=
parseList(tok::r_square, LSquareLoc, RSquareLoc,
/*AllowSepAfterLast=*/true,
diag::expected_rsquare_array_expr,
SyntaxKind::DictionaryElementList,
[&]() -> ParserStatus {
// Parse the next key.
ParserResult<Expr> Key;

Key = parseExpr(diag::expected_key_in_dictionary_literal);
if (Key.isNull())
return Key;

// Parse the ':'.
if (Tok.isNot(tok::colon)) {
diagnose(Tok, diag::expected_colon_in_dictionary_literal);
return ParserStatus(Key) | makeParserError();
}
consumeToken();
if (!isDictionary.hasValue())
isDictionary = Tok.is(tok::colon);

// Parse the next value.
ParserResult<Expr> Value =
parseExpr(diag::expected_value_in_dictionary_literal);
if (!*isDictionary)
return Element;

if (Value.isNull())
Value = makeParserResult(Value, new (Context) ErrorExpr(PreviousLoc));
if (Element.isNull())
return Element;

// Add this key/value pair.
addKeyValuePair(Key.get(), Value.get());
// Parse the ':'.
if (!consumeIf(tok::colon)) {
diagnose(Tok, diag::expected_colon_in_dictionary_literal);
return ParserStatus(Element) | makeParserError();
}

if (Tok.is(tok::comma))
CommaLocs.push_back(Tok.getLoc());
// Parse the value.
auto Value = parseExpr(diag::expected_value_in_dictionary_literal);

return ParserStatus(Key) | ParserStatus(Value);
});
if (Value.isNull())
Value = makeParserResult(Value, new (Context) ErrorExpr(PreviousLoc));

assert(SubExprs.size() >= 1);
return makeParserResult(Status, DictionaryExpr::create(Context, LSquareLoc,
SubExprs, CommaLocs,
RSquareLoc));
// Make a tuple of Key Value pair.
return makeParserResult(
ParserStatus(Element) | ParserStatus(Value),
TupleExpr::createImplicit(Context, {Element.get(), Value.get()}, {}));
}

void Parser::addPatternVariablesToScope(ArrayRef<Pattern *> Patterns) {
Expand Down
2 changes: 1 addition & 1 deletion test/Constraints/dictionary_literal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func testDefaultExistentials() {
// SR-4952, rdar://problem/32330004 - Assertion failure during swift::ASTVisitor<::FailureDiagnosis,...>::visit
func rdar32330004_1() -> [String: Any] {
return ["a""one": 1, "two": 2, "three": 3] // expected-note {{did you mean to use a dictionary literal instead?}}
// expected-error@-1 2 {{expected ',' separator}}
// expected-error@-1 {{expected ',' separator}}
// expected-error@-2 {{dictionary of type '[String : Any]' cannot be used with array literal}}
}

Expand Down
2 changes: 1 addition & 1 deletion test/Profiler/coverage_closures.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sil -module-name coverage_closures %s | %FileCheck %s

func bar(arr: [(Int32) -> Int32]) {
// CHECK-LABEL: sil_coverage_map {{.*}}// closure #2 (Swift.Int32) -> Swift.Int32 in coverage_closures.bar
// CHECK-LABEL: sil_coverage_map {{.*}}// closure #1 (Swift.Int32) -> Swift.Int32 in coverage_closures.bar
// CHECK-NEXT: [[@LINE+1]]:13 -> [[@LINE+1]]:42 : 0
for a in [{ (b : Int32) -> Int32 in b }] {
a(0)
Expand Down
14 changes: 13 additions & 1 deletion test/SILGen/local_types.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
// RUN: %target-swift-emit-silgen -parse-as-library -enable-sil-ownership %s -verify | %FileCheck %s
// RUN: %target-swift-emit-ir -parse-as-library -enable-sil-ownership %s

func function() {
func function1() {
return

class UnreachableClass {} // expected-warning {{code after 'return' will never be executed}}
}

func function2() {
let _ = [
{
struct S {
var x = 0
}
}
]
}

// CHECK-LABEL: sil private [transparent] @$s11local_types9function2yyFyycfU_1SL_V1xSivpfi : $@convention(thin) () -> Int

// CHECK-LABEL: sil_vtable UnreachableClass
2 changes: 1 addition & 1 deletion test/Syntax/diagnostics_verify.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ if true {
if false {
[.]
// expected-error@-1 {{expected identifier after '.' expression}}
// expected-error@-2 2 {{unknown expression syntax exists in the source}}
// expected-error@-2 {{unknown expression syntax exists in the source}}
}

class { // expected-error {{unknown declaration syntax exists in the source}}
Expand Down
9 changes: 9 additions & 0 deletions validation-test/compiler_scale/parse_array_nested.swift.gyb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// RUN: %scale-test -parse --begin 0 --end 10 --step 1 --select NumASTBytes %s

%for i in range(1, N + 1):
[
%end
1
%for i in range(1, N + 1):
]
%end