Skip to content

Commit b119407

Browse files
committed
[Parse] Change whitespace rule between attribute name and '(' in Swift 6
In Swift 6 and later, whitespace is no longer permitted between an attribute name and the opening parenthesis. Update the parser so that in Swift 6+, a `(` without preceding whitespace is always treated as the start of the argument list, while a '(' with preceding whitespace is considered the start of the argument list _only when_ it is unambiguous. This change makes the following closure be parsed correctly: { @mainactor (arg: Int) in ... } rdar://147785544
1 parent b72ef8e commit b119407

File tree

9 files changed

+135
-72
lines changed

9 files changed

+135
-72
lines changed

include/swift/Parse/Parser.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,11 @@ class Parser {
574574
/// the '(' by a space.
575575
///
576576
/// If the next token is not '(' or it's on a new line, return false.
577-
bool consumeIfAttributeLParen();
577+
bool consumeIfAttributeLParen(bool isCustomAttr = false);
578+
579+
/// Check if the current token is '(' and it looks like a start of an
580+
/// attribute argument list.
581+
bool isAtAttributeLParen(bool isCustomAttr = false);
578582

579583
bool consumeIfNotAtStartOfLine(tok K) {
580584
if (Tok.isAtStartOfLine()) return false;
@@ -1147,7 +1151,6 @@ class Parser {
11471151
SourceLoc AtEndLoc,
11481152
bool isFromClangAttribute = false);
11491153

1150-
bool isCustomAttributeArgument();
11511154
bool canParseCustomAttribute();
11521155

11531156
/// Parse a custom attribute after the initial '@'.

lib/Parse/ParseDecl.cpp

Lines changed: 27 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ bool Parser::parseSpecializeAttribute(
877877
assert(ClosingBrace == tok::r_paren || ClosingBrace == tok::r_square);
878878

879879
SourceLoc lParenLoc;
880-
if (Tok.is(tok::l_paren)) {
880+
if (isAtAttributeLParen()) {
881881
lParenLoc = consumeAttributeLParen();
882882
} else {
883883
// SIL parsing is positioned at _specialize when entering this and parses
@@ -2261,7 +2261,7 @@ Parser::parseMacroRoleAttribute(
22612261
break;
22622262
}
22632263

2264-
if (!Tok.isFollowingLParen()) {
2264+
if (!isAtAttributeLParen()) {
22652265
diagnose(Tok, diag::attr_expected_lparen, attrName, false);
22662266
return makeParserError();
22672267
}
@@ -2503,7 +2503,7 @@ static std::optional<Identifier> parseSingleAttrOptionImpl(
25032503
};
25042504
bool isDeclModifier = DeclAttribute::isDeclModifier(DK);
25052505

2506-
if (!P.Tok.isFollowingLParen()) {
2506+
if (!P.isAtAttributeLParen()) {
25072507
if (allowOmitted)
25082508
return Identifier();
25092509

@@ -2883,7 +2883,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
28832883
.Case("public", AccessLevel::Public)
28842884
.Case("open", AccessLevel::Open);
28852885

2886-
if (!Tok.isFollowingLParen()) {
2886+
if (!isAtAttributeLParen()) {
28872887
// Normal access control attribute.
28882888
AttrRange = Loc;
28892889

@@ -3467,7 +3467,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
34673467
}
34683468
case DeclAttrKind::PrivateImport: {
34693469
// Parse the leading '('.
3470-
if (!Tok.isFollowingLParen()) {
3470+
if (!isAtAttributeLParen()) {
34713471
diagnose(Loc, diag::attr_expected_lparen, AttrName,
34723472
DeclAttribute::isDeclModifier(DK));
34733473
return makeParserSuccess();
@@ -3516,7 +3516,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
35163516
}
35173517
case DeclAttrKind::ObjC: {
35183518
// Unnamed @objc attribute.
3519-
if (!Tok.isFollowingLParen()) {
3519+
if (!isAtAttributeLParen()) {
35203520
auto attr = ObjCAttr::createUnnamed(Context, AtLoc, Loc);
35213521
Attributes.add(attr);
35223522
break;
@@ -3584,7 +3584,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
35843584

35853585
case DeclAttrKind::DynamicReplacement: {
35863586
// Parse the leading '('.
3587-
if (!Tok.isFollowingLParen()) {
3587+
if (!isAtAttributeLParen()) {
35883588
diagnose(Loc, diag::attr_expected_lparen, AttrName,
35893589
DeclAttribute::isDeclModifier(DK));
35903590
return makeParserSuccess();
@@ -3635,7 +3635,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
36353635

36363636
case DeclAttrKind::TypeEraser: {
36373637
// Parse leading '('
3638-
if (!Tok.isFollowingLParen()) {
3638+
if (!isAtAttributeLParen()) {
36393639
diagnose(Loc, diag::attr_expected_lparen, AttrName,
36403640
DeclAttribute::isDeclModifier(DK));
36413641
return makeParserSuccess();
@@ -3665,7 +3665,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
36653665

36663666
case DeclAttrKind::Specialize:
36673667
case DeclAttrKind::Specialized: {
3668-
if (!Tok.isFollowingLParen()) {
3668+
if (!isAtAttributeLParen()) {
36693669
diagnose(Loc, diag::attr_expected_lparen, AttrName,
36703670
DeclAttribute::isDeclModifier(DK));
36713671
return makeParserSuccess();
@@ -3894,7 +3894,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
38943894
break;
38953895
}
38963896
case DeclAttrKind::RawLayout: {
3897-
if (!Tok.isFollowingLParen()) {
3897+
if (!isAtAttributeLParen()) {
38983898
diagnose(Loc, diag::attr_expected_lparen, AttrName,
38993899
DeclAttribute::isDeclModifier(DK));
39003900
return makeParserSuccess();
@@ -4164,27 +4164,11 @@ bool Parser::parseVersionTuple(llvm::VersionTuple &Version,
41644164
return false;
41654165
}
41664166

4167-
bool Parser::isCustomAttributeArgument() {
4168-
BacktrackingScope backtrack(*this);
4169-
if (skipSingle().hasCodeCompletion())
4170-
return true;
4171-
4172-
// If we have any keyword, identifier, or token that follows a function
4173-
// type's parameter list, this is a parameter list and not an attribute.
4174-
// Alternatively, we might have a token that illustrates we're not going to
4175-
// get anything following the attribute, which means the parentheses describe
4176-
// what follows the attribute.
4177-
return !Tok.isAny(
4178-
tok::arrow, tok::kw_throw, tok::kw_throws, tok::kw_rethrows,
4179-
tok::r_paren, tok::r_brace, tok::r_square, tok::r_angle) &&
4180-
!Tok.isContextualKeyword("async") && !Tok.isContextualKeyword("reasync") ;
4181-
}
4182-
41834167
bool Parser::canParseCustomAttribute() {
41844168
if (!canParseType())
41854169
return false;
41864170

4187-
if (Tok.isFollowingLParen() && isCustomAttributeArgument())
4171+
if (isAtAttributeLParen(/*isCustomAttribute=*/true))
41884172
skipSingle();
41894173

41904174
return true;
@@ -4196,7 +4180,7 @@ ParserResult<CustomAttr> Parser::parseCustomAttribute(SourceLoc atLoc) {
41964180
// Parse a custom attribute.
41974181
auto type = parseType(diag::expected_type, ParseTypeReason::CustomAttribute);
41984182
if (type.hasCodeCompletion() || type.isNull()) {
4199-
if (Tok.isFollowingLParen() && isCustomAttributeArgument())
4183+
if (isAtAttributeLParen(/*isCustomAttribute=*/true))
42004184
skipSingle();
42014185

42024186
return ParserResult<CustomAttr>(ParserStatus(type));
@@ -4208,7 +4192,11 @@ ParserResult<CustomAttr> Parser::parseCustomAttribute(SourceLoc atLoc) {
42084192
ParserStatus status;
42094193
ArgumentList *argList = nullptr;
42104194
CustomAttributeInitializer *initContext = nullptr;
4211-
if (Tok.isFollowingLParen() && isCustomAttributeArgument()) {
4195+
if (isAtAttributeLParen(/*isCustomAttribute=*/true)) {
4196+
if (getEndOfPreviousLoc() != Tok.getLoc()) {
4197+
diagnose(getEndOfPreviousLoc(), diag::attr_extra_whitespace_before_lparen)
4198+
.warnUntilSwiftVersion(6);
4199+
}
42124200
// If we have no local context to parse the initial value into, create
42134201
// one for the attribute.
42144202
std::optional<ParseFunctionBody> initParser;
@@ -4217,10 +4205,6 @@ ParserResult<CustomAttr> Parser::parseCustomAttribute(SourceLoc atLoc) {
42174205
initContext = CustomAttributeInitializer::create(CurDeclContext);
42184206
initParser.emplace(*this, initContext);
42194207
}
4220-
if (getEndOfPreviousLoc() != Tok.getLoc()) {
4221-
diagnose(getEndOfPreviousLoc(), diag::attr_extra_whitespace_before_lparen)
4222-
.warnUntilSwiftVersion(6);
4223-
}
42244208
auto result = parseArgumentList(tok::l_paren, tok::r_paren,
42254209
/*isExprBasic*/ true,
42264210
/*allowTrailingClosure*/ false);
@@ -4378,7 +4362,7 @@ ParserStatus Parser::parseDeclAttribute(DeclAttributes &Attributes,
43784362
SourceLoc attrLoc = consumeToken();
43794363

43804364
// @warn_unused_result with no arguments.
4381-
if (!Tok.isFollowingLParen()) {
4365+
if (!isAtAttributeLParen()) {
43824366
diagnose(AtLoc, diag::attr_warn_unused_result_removed)
43834367
.fixItRemove(SourceRange(AtLoc, attrLoc));
43844368

@@ -4462,7 +4446,7 @@ ParserStatus Parser::parseDeclAttribute(DeclAttributes &Attributes,
44624446

44634447
// Recover by eating @foo(...) when foo is not known.
44644448
consumeToken();
4465-
if (Tok.isFollowingLParen())
4449+
if (isAtAttributeLParen())
44664450
skipSingle();
44674451

44684452
return makeParserError();
@@ -4745,15 +4729,8 @@ ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result,
47454729
// Recover by eating @foo(...) when foo is not known.
47464730
consumeToken();
47474731

4748-
if (Tok.is(tok::l_paren) && getEndOfPreviousLoc() == Tok.getLoc()) {
4749-
CancellableBacktrackingScope backtrack(*this);
4732+
if (isAtAttributeLParen(/*isCustomAttr=*/true))
47504733
skipSingle();
4751-
// If we found '->', or 'throws' after paren, it's likely a parameter
4752-
// of function type.
4753-
if (Tok.isNot(tok::arrow, tok::kw_throws, tok::kw_rethrows,
4754-
tok::kw_throw))
4755-
backtrack.cancelBacktrack();
4756-
}
47574734

47584735
return makeParserError();
47594736
}
@@ -4814,7 +4791,7 @@ ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result,
48144791

48154792
case TypeAttrKind::Isolated: {
48164793
SourceLoc lpLoc = Tok.getLoc(), kindLoc, rpLoc;
4817-
if (!consumeIfNotAtStartOfLine(tok::l_paren)) {
4794+
if (!consumeIfAttributeLParen()) {
48184795
if (!justChecking) {
48194796
diagnose(Tok, diag::attr_isolated_expected_lparen);
48204797
// TODO: should we suggest removing the `@`?
@@ -4862,7 +4839,7 @@ ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result,
48624839
case TypeAttrKind::Opened: {
48634840
// Parse the opened existential ID string in parens
48644841
SourceLoc beginLoc = Tok.getLoc(), idLoc, endLoc;
4865-
if (!consumeAttributeLParen()) {
4842+
if (!consumeIfAttributeLParen()) {
48664843
if (!justChecking)
48674844
diagnose(Tok, diag::opened_attribute_expected_lparen);
48684845
return makeParserError();
@@ -5069,7 +5046,7 @@ ParserResult<LifetimeEntry> Parser::parseLifetimeEntry(SourceLoc loc) {
50695046
return std::nullopt;
50705047
};
50715048

5072-
if (!Tok.isFollowingLParen()) {
5049+
if (!isAtAttributeLParen()) {
50735050
diagnose(loc, diag::expected_lparen_after_lifetime_dependence);
50745051
status.setIsParseError();
50755052
return status;
@@ -5777,6 +5754,7 @@ static bool consumeIfParenthesizedNonisolated(Parser &P) {
57775754
}
57785755

57795756
static void skipAttribute(Parser &P) {
5757+
P.consumeToken(tok::at_sign);
57805758
// Consider unexpected tokens to be incomplete attributes.
57815759

57825760
// Parse the attribute name, which can be qualified, have
@@ -5793,12 +5771,8 @@ static void skipAttribute(Parser &P) {
57935771
} while (P.consumeIf(tok::period));
57945772

57955773
// Skip an argument clause after the attribute name.
5796-
if (P.consumeIf(tok::l_paren)) {
5797-
while (P.Tok.isNot(tok::r_brace, tok::eof, tok::pound_endif)) {
5798-
if (P.consumeIf(tok::r_paren)) break;
5799-
P.skipSingle();
5800-
}
5801-
}
5774+
if (P.isAtAttributeLParen())
5775+
P.skipSingle();
58025776
}
58035777

58045778
bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes,
@@ -5842,7 +5816,7 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes,
58425816
// in positions like generic argument lists.
58435817
if (Tok.is(tok::at_sign)) {
58445818
BacktrackingScope backtrack(*this);
5845-
while (consumeIf(tok::at_sign))
5819+
while (Tok.is(tok::at_sign))
58465820
skipAttribute(*this);
58475821

58485822
// If this attribute is the last element in the block,

lib/Parse/ParseStmt.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ static bool isAtStartOfSwitchCase(Parser &parser,
230230

231231
parser.consumeToken(tok::at_sign);
232232
parser.consumeToken(tok::identifier);
233-
if (parser.Tok.is(tok::l_paren))
233+
if (parser.isAtAttributeLParen())
234234
parser.skipSingle();
235235
}
236236

@@ -404,7 +404,10 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
404404
ParserStatus Status = parseLineDirective(false);
405405
BraceItemsStatus |= Status;
406406
NeedParseErrorRecovery = Status.isErrorOrHasCompletion();
407-
} else if (isStartOfSwiftDecl()) {
407+
} else if (isStartOfSwiftDecl() ||
408+
// Force parsing '@<identifier>' as a declaration, as there's no
409+
// valid expression or statement starting with an attribute.
410+
(Tok.is(tok::at_sign) && peekToken().is(tok::identifier))) {
408411
SmallVector<Decl*, 8> TmpDecls;
409412
ParserStatus DeclResult =
410413
parseDecl(IsAtStartOfLineOrPreviousHadSemi,
@@ -2711,7 +2714,7 @@ ParserResult<CaseStmt> Parser::parseStmtCase(bool IsActive) {
27112714
}
27122715
consumeToken(tok::identifier);
27132716

2714-
if (Tok.is(tok::l_paren)) {
2717+
if (isAtAttributeLParen()) {
27152718
diagnose(Tok, diag::unexpected_lparen_in_attribute, "unknown");
27162719
skipSingle();
27172720
}
@@ -2722,7 +2725,7 @@ ParserResult<CaseStmt> Parser::parseStmtCase(bool IsActive) {
27222725
diagnose(Tok, diag::unknown_attr_name, Tok.getText());
27232726
consumeToken(tok::identifier);
27242727

2725-
if (Tok.is(tok::l_paren))
2728+
if (isAtAttributeLParen())
27262729
skipSingle();
27272730
}
27282731
}

lib/Parse/Parser.cpp

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -622,14 +622,49 @@ SourceLoc Parser::consumeAttributeLParen() {
622622
return consumeToken(tok::l_paren);
623623
}
624624

625-
bool Parser::consumeIfAttributeLParen() {
626-
if (!Tok.isFollowingLParen()) {
625+
bool Parser::consumeIfAttributeLParen(bool isCustomAttr) {
626+
if (!isAtAttributeLParen(isCustomAttr))
627627
return false;
628-
}
629-
consumeAttributeLParen();
628+
(void)consumeAttributeLParen();
630629
return true;
631630
}
632631

632+
bool Parser::isAtAttributeLParen(bool isCustomAttr) {
633+
if (!Tok.isFollowingLParen())
634+
return false;
635+
636+
if (Context.isSwiftVersionAtLeast(6)) {
637+
// No-space '(' are always arguments.
638+
if (getEndOfPreviousLoc() == Tok.getLoc())
639+
return true;
640+
641+
// Otherwise it's an error, but for recovery, parse it as an argument list
642+
// if it's obvious.
643+
BacktrackingScope backtrack(*this);
644+
skipSingle();
645+
return Tok.is(tok::at_sign) || isStartOfSwiftDecl();
646+
} else {
647+
// In <=5, builtin attributes only checks 'isFollowingLParen()'.
648+
if (!isCustomAttr)
649+
return true;
650+
651+
BacktrackingScope backtrack(*this);
652+
if (skipSingle().hasCodeCompletion())
653+
return true;
654+
655+
// If we have any keyword, identifier, or token that follows a function
656+
// type's parameter list, this is a parameter list and not an attribute.
657+
// Alternatively, we might have a token that illustrates we're not going to
658+
// get anything following the attribute, which means the parentheses
659+
// describe what follows the attribute.
660+
return (!Tok.isAny(tok::arrow, tok::kw_throw, tok::kw_throws,
661+
tok::kw_rethrows, tok::r_paren, tok::r_brace,
662+
tok::r_square, tok::r_angle) &&
663+
!Tok.isContextualKeyword("async") &&
664+
!Tok.isContextualKeyword("reasync"));
665+
}
666+
}
667+
633668
SourceLoc Parser::consumeStartingCharacterOfCurrentToken(tok Kind, size_t Len) {
634669
// Consumes prefix of token and returns its location.
635670
// (like '?', '<', '>' or '!' immediately followed by '<')

test/Macros/macro_self.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// RUN: %target-swift-frontend -parse %s -verify
22

3-
@freestanding(expression) // expected-error {{expected expression}}
3+
@freestanding(expression)
44
macro self() = #externalMacro(module: "MacroDefinition", type: "InvalidMacro")
5+
// expected-error@-1 {{expected declaration}}
56

67
func sync() {}
78

8-
@freestanding(expression) // expected-error {{expected expression}}
9+
@freestanding(expression)
910
macro Self() = #externalMacro(module: "MacroDefinition", type: "InvalidMacro")
11+
// expected-error@-1 {{expected declaration}}
1012

1113
func testSelfAsFreestandingMacro() {
1214
_ = #self

0 commit comments

Comments
 (0)