Skip to content

Commit 013997d

Browse files
Implement trailing comma for comma-separated lists
1 parent b810077 commit 013997d

11 files changed

+263
-7
lines changed

include/swift/Basic/Features.def

+3
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ EXPERIMENTAL_FEATURE(DebugDescriptionMacro, true)
401401

402402
EXPERIMENTAL_FEATURE(ReinitializeConsumeInMultiBlockDefer, false)
403403

404+
// Enable trailing comma for comma-separated lists.
405+
EXPERIMENTAL_FEATURE(TrailingComma, true)
406+
404407
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
405408
#undef EXPERIMENTAL_FEATURE
406409
#undef UPCOMING_FEATURE

include/swift/Parse/Parser.h

+2
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,8 @@ class Parser {
19661966
/// expression can be parsed.
19671967
bool isStartOfStmt(bool preferExpr);
19681968

1969+
bool isStartOfConditionalStmtBody();
1970+
19691971
bool isTerminatorForBraceItemListKind(BraceItemListKind Kind,
19701972
ArrayRef<ASTNode> ParsedDecls);
19711973
ParserResult<Stmt> parseStmt(bool fromASTGen = false);

lib/AST/FeatureSet.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ static bool usesFeatureSensitive(Decl *decl) {
771771

772772
UNINTERESTING_FEATURE(DebugDescriptionMacro)
773773
UNINTERESTING_FEATURE(ReinitializeConsumeInMultiBlockDefer)
774+
UNINTERESTING_FEATURE(TrailingComma)
774775

775776
// ----------------------------------------------------------------------------
776777
// MARK: - FeatureSet

lib/ASTGen/Sources/ASTGen/SourceFile.swift

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ extension Parser.ExperimentalFeatures {
6262
mapFeature(.NonescapableTypes, to: .nonescapableTypes)
6363
mapFeature(.TransferringArgsAndResults, to: .transferringArgsAndResults)
6464
mapFeature(.SendingArgsAndResults, to: .sendingArgsAndResults)
65+
mapFeature(.TrailingComma, to: .trailingComma)
6566
}
6667
}
6768

lib/Parse/ParseDecl.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -2549,7 +2549,7 @@ Parser::parseMacroRoleAttribute(
25492549
SmallVector<MacroIntroducedDeclName, 2> names;
25502550
SmallVector<TypeExpr *, 2> conformances;
25512551
auto argumentsStatus = parseList(tok::r_paren, lParenLoc, rParenLoc,
2552-
/*AllowSepAfterLast=*/false,
2552+
/*AllowSepAfterLast=*/Context.LangOpts.hasFeature(Feature::TrailingComma),
25532553
diag::expected_rparen_expr_list,
25542554
[&] {
25552555
ParserStatus status;
@@ -9351,6 +9351,10 @@ Parser::parseDeclEnumCase(ParseDeclOptions Flags,
93519351
if (!Tok.is(tok::comma))
93529352
break;
93539353
CommaLoc = consumeToken(tok::comma);
9354+
if (Context.LangOpts.hasFeature(Feature::TrailingComma) &&
9355+
(Tok.isAtStartOfLine() || Tok.is(tok::r_brace) || Tok.is(tok::semi))) {
9356+
break;
9357+
}
93549358
}
93559359

93569360
if (!(Flags & PD_AllowEnumElement)) {

lib/Parse/ParseExpr.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -1914,6 +1914,10 @@ ParserResult<Expr> Parser::parseExprPrimary(Diag<> ID, bool isExprBasic) {
19141914
default:
19151915
UnknownCharacter:
19161916
checkForInputIncomplete();
1917+
if (Context.LangOpts.hasFeature(Feature::TrailingComma) &&
1918+
Tok.is(tok::eof) && Tok.getText() == ")") {
1919+
return nullptr;
1920+
}
19171921
// FIXME: offer a fixit: 'Self' -> 'self'
19181922
diagnose(Tok, ID);
19191923
return nullptr;
@@ -2758,7 +2762,8 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
27582762
VD->setIsSelfParamCapture();
27592763

27602764
captureList.push_back(CLE);
2761-
} while (HasNext);
2765+
} while (HasNext && !(Context.LangOpts.hasFeature(Feature::TrailingComma) &&
2766+
Tok.is(tok::r_square)));
27622767

27632768
// The capture list needs to be closed off with a ']'.
27642769
SourceLoc rBracketLoc = Tok.getLoc();
@@ -3278,7 +3283,7 @@ ParserStatus Parser::parseExprList(tok leftTok, tok rightTok,
32783283
StructureMarkerRAII ParsingExprList(*this, Tok);
32793284

32803285
leftLoc = consumeToken(leftTok);
3281-
return parseList(rightTok, leftLoc, rightLoc, /*AllowSepAfterLast=*/false,
3286+
return parseList(rightTok, leftLoc, rightLoc, /*AllowSepAfterLast=*/Context.LangOpts.hasFeature(Feature::TrailingComma),
32823287
rightTok == tok::r_paren ? diag::expected_rparen_expr_list
32833288
: diag::expected_rsquare_expr_list,
32843289
[&] () -> ParserStatus {

lib/Parse/ParseGeneric.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Parser::parseGenericParametersBeforeWhere(SourceLoc LAngleLoc,
4747
SmallVectorImpl<GenericTypeParamDecl *> &GenericParams) {
4848
ParserStatus Result;
4949
bool HasNextParam{};
50+
bool IsEndOfList;
5051
do {
5152
// Note that we're parsing a declaration.
5253
StructureMarkerRAII ParsingDecl(*this, Tok.getLoc(),
@@ -132,7 +133,9 @@ Parser::parseGenericParametersBeforeWhere(SourceLoc LAngleLoc,
132133

133134
// Parse the comma, if the list continues.
134135
HasNextParam = consumeIf(tok::comma);
135-
} while (HasNextParam);
136+
IsEndOfList = (Context.LangOpts.hasFeature(Feature::TrailingComma) &&
137+
startsWithGreater(Tok));
138+
} while (HasNextParam && !IsEndOfList);
136139

137140
return Result;
138141
}
@@ -276,6 +279,7 @@ ParserStatus Parser::parseGenericWhereClause(
276279
// Parse the 'where'.
277280
WhereLoc = consumeToken(tok::kw_where);
278281
bool HasNextReq;
282+
bool IsEndOfList;
279283
do {
280284
if (Tok.is(tok::code_complete)) {
281285
if (CodeCompletionCallbacks) {
@@ -401,6 +405,8 @@ ParserStatus Parser::parseGenericWhereClause(
401405
break;
402406
}
403407
HasNextReq = consumeIf(tok::comma);
408+
IsEndOfList = (Context.LangOpts.hasFeature(Feature::TrailingComma) &&
409+
Tok.is(tok::l_brace));
404410
// If there's a comma, keep parsing the list.
405411
// If there's a "&&", diagnose replace with a comma and keep parsing
406412
if (Tok.isBinaryOperator() && Tok.getText() == "&&" && !HasNextReq) {
@@ -409,7 +415,7 @@ ParserStatus Parser::parseGenericWhereClause(
409415
consumeToken();
410416
HasNextReq = true;
411417
}
412-
} while (HasNextReq);
418+
} while (HasNextReq && !IsEndOfList);
413419

414420
if (!Requirements.empty())
415421
EndLoc = Requirements.back().getSourceRange().End;

lib/Parse/ParsePattern.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ Parser::parseParameterClause(SourceLoc &leftParenLoc,
192192
// Parse the parameter list.
193193
bool isClosure = paramContext == ParameterContextKind::Closure;
194194
return parseList(tok::r_paren, leftParenLoc, rightParenLoc,
195-
/*AllowSepAfterLast=*/false,
195+
/*AllowSepAfterLast=*/Context.LangOpts.hasFeature(Feature::TrailingComma),
196196
diag::expected_rparen_parameter,
197197
[&]() -> ParserStatus {
198198
ParsedParameter param;
@@ -1279,7 +1279,7 @@ ParserResult<Pattern> Parser::parsePatternTuple() {
12791279
SmallVector<TuplePatternElt, 8> elts;
12801280
ParserStatus ListStatus =
12811281
parseList(tok::r_paren, LPLoc, RPLoc,
1282-
/*AllowSepAfterLast=*/false,
1282+
/*AllowSepAfterLast=*/Context.LangOpts.hasFeature(Feature::TrailingComma),
12831283
diag::expected_rparen_tuple_pattern_list,
12841284
[&] () -> ParserStatus {
12851285
// Parse the pattern tuple element.

lib/Parse/ParseStmt.cpp

+92
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,11 @@ Parser::parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs,
15191519
consumeToken();
15201520
Status.setIsParseError();
15211521
} else if (consumeIf(tok::comma)) {
1522+
// End of list with a trailing comma.
1523+
if (Context.LangOpts.hasFeature(Feature::TrailingComma) &&
1524+
Tok.is(tok::r_paren)) {
1525+
break;
1526+
}
15221527
// There is more to parse in this list.
15231528

15241529
// Before continuing to parse the next specification, we check that it's
@@ -1833,6 +1838,74 @@ Parser::parseStmtConditionElement(SmallVectorImpl<StmtConditionElement> &result,
18331838
return Status;
18341839
}
18351840

1841+
/// Returns `true` if the current token represents the start of a conditional
1842+
/// statement body.
1843+
bool Parser::isStartOfConditionalStmtBody() {
1844+
if (!Tok.is(tok::l_brace)) {
1845+
// Statement bodies always start with a '{'. If there is no '{', we can't be
1846+
// at the statement body.
1847+
return false;
1848+
}
1849+
1850+
Parser::BacktrackingScope Backtrack(*this);
1851+
1852+
skipSingle();
1853+
1854+
if (Tok.is(tok::eof)) {
1855+
// There's nothing else in the source file that could be the statement body,
1856+
// so this must be it.
1857+
return true;
1858+
}
1859+
1860+
if (Tok.is(tok::semi)) {
1861+
// We can't have a semicolon between the condition and the statement body,
1862+
// so this must be the statement body.
1863+
return true;
1864+
}
1865+
1866+
if (Tok.is(tok::kw_else)) {
1867+
// If the current token is an `else` keyword, this must be the statement
1868+
// body of an `if` statement since conditions can't be followed by `else`.
1869+
return true;
1870+
}
1871+
1872+
if (Tok.is(tok::r_brace) || Tok.is(tok::r_paren)) {
1873+
// A right brace or parenthesis cannot start a statement body, nor can the
1874+
// condition list continue afterwards. So, this must be the statement body.
1875+
// This covers cases like `if true, { if true, { } }` or `( if true, {
1876+
// print(0) } )`. While the latter is not valid code, it improves
1877+
// diagnostics.
1878+
return true;
1879+
}
1880+
1881+
if (Tok.isAtStartOfLine()) {
1882+
// If the current token is at the start of a line, it is most likely a
1883+
// statement body. The only exceptions are:
1884+
if (Tok.is(tok::comma)) {
1885+
// If newline begins with ',' it must be a condition trailing comma, so
1886+
// this can't be the statement body, e.g. if true, { true } , true {
1887+
// print("body") }
1888+
return false;
1889+
}
1890+
if (Tok.is(tok::oper_binary_spaced)) {
1891+
// If current token is a binary operator this can't be the statement body
1892+
// since an `if` expression can't be the left-hand side of an operator,
1893+
// e.g. if true, { true }
1894+
// != nil
1895+
// {
1896+
// print("body")
1897+
// }
1898+
return false;
1899+
}
1900+
// Excluded the above exceptions, this must be the statement body.
1901+
return true;
1902+
} else {
1903+
// If the current token isn't at the start of a line and isn't `EOF`, `;`,
1904+
// `else`, `)` or `}` this can't be the statement body.
1905+
return false;
1906+
}
1907+
}
1908+
18361909
/// Parse the condition of an 'if' or 'while'.
18371910
///
18381911
/// condition:
@@ -1861,6 +1934,20 @@ ParserStatus Parser::parseStmtCondition(StmtCondition &Condition,
18611934
// a variety of common errors situations (including migrating from Swift 2
18621935
// syntax).
18631936
while (true) {
1937+
1938+
if (Context.LangOpts.hasFeature(Feature::TrailingComma)) {
1939+
// Condition terminator is `else` for `guard` statements.
1940+
if (ParentKind == StmtKind::Guard && Tok.is(tok::kw_else)) {
1941+
break;
1942+
}
1943+
// Condition terminator is start of statement body for `if` or `while`
1944+
// statements. Missing `else` is a common mistake for `guard` statements
1945+
// so we fall back to lookahead for a body.
1946+
if (isStartOfConditionalStmtBody()) {
1947+
break;
1948+
}
1949+
}
1950+
18641951
Status |= parseStmtConditionElement(result, DefaultID, ParentKind,
18651952
BindingKindStr);
18661953
if (Status.isErrorOrHasCompletion())
@@ -2634,6 +2721,11 @@ static ParserStatus parseStmtCase(Parser &P, SourceLoc &CaseLoc,
26342721
if (!P.consumeIf(tok::comma))
26352722
break;
26362723

2724+
if (P.Context.LangOpts.hasFeature(Feature::TrailingComma) &&
2725+
P.Tok.is(tok::colon)) {
2726+
break;
2727+
}
2728+
26372729
if (P.Tok.is(tok::kw_case)) {
26382730
P.diagnose(P.Tok, diag::extra_case_keyword)
26392731
.fixItRemove(SourceRange(P.Tok.getLoc()));
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// RUN: %target-typecheck-verify-swift -disable-experimental-parser-round-trip -enable-experimental-feature TrailingComma
2+
3+
func f(_ block: (Bool) -> Bool) -> Bool { block(true) }
4+
5+
func testConditionListTrailingComma() {
6+
if true, { }
7+
8+
if true, { }; { }()
9+
10+
if true, { print("if-body") } else { print("else-body") }
11+
12+
if true, { print("if-body") } else if true, { print("else-if-body") } else { print("else-body") }
13+
14+
if true, { if true { { } } } // expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}}
15+
16+
{ if true, { print(0) } } // expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}}
17+
18+
( if true, { print(0) } ) // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} expected-error {{'if' must have an unconditional 'else' to be used as expression}}
19+
20+
if true, {true}(), { }
21+
22+
if true, { true }, { print(0) } // expected-error {{function produces expected type 'Bool'; did you mean to call it with '()'?}}
23+
24+
if true, { print(0) }
25+
{ }()
26+
27+
if true, { true } // expected-error {{function produces expected type 'Bool'; did you mean to call it with '()'?}}
28+
,{ print(0) }
29+
30+
if true, { (x: () -> Void) in true } != nil { print(0) } // expected-warning {{comparing non-optional value of type '(() -> Void) -> Bool' to 'nil' always returns true}}
31+
32+
if true, { (x: () -> Void) in true }
33+
!= nil // expected-warning {{comparing non-optional value of type '(() -> Void) -> Bool' to 'nil' always returns true}}
34+
{
35+
print(0)
36+
}
37+
38+
if { } // expected-error {{missing condition in 'if' statement}}
39+
40+
if , { } // expected-error {{expected expression, var, or let in 'if' condition}}
41+
42+
guard else { return } // expected-error {{missing condition in 'guard' statement}}
43+
44+
guard , else { return } // expected-error {{expected expression, var, let or case in 'guard' condition}}
45+
46+
guard true, else { return }
47+
48+
guard true, , else { return } // expected-error {{expected expression in conditional}}
49+
50+
guard true, { return } // expected-error {{expected 'else' after 'guard' condition}}
51+
}

0 commit comments

Comments
 (0)