Skip to content

Commit 16c4014

Browse files
committed
[SwiftParser] Update whitespace rule between attribute name and '('
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: ```swift { @mainactor (arg: Int) in ... } ``` rdar://147785544
1 parent 6df106d commit 16c4014

File tree

7 files changed

+196
-115
lines changed

7 files changed

+196
-115
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -196,28 +196,35 @@ extension Parser {
196196
atSign = atSign.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
197197
}
198198
let attributeName = self.parseAttributeName()
199+
let attributeNameHasTrailingSpace = attributeName.raw.trailingTriviaByteLength > 0
200+
199201
let shouldParseArgument: Bool
200202
switch argumentMode {
201203
case .required:
202204
shouldParseArgument = true
203205
case .customAttribute:
204-
shouldParseArgument = self.withLookahead { $0.atAttributeOrSpecifierArgument() }
206+
shouldParseArgument = self.withLookahead {
207+
$0.atAttributeOrSpecifierArgument(lastTokenHadSpace: attributeNameHasTrailingSpace, forCustomAttribute: true)
208+
}
205209
case .optional:
206-
shouldParseArgument = self.at(TokenSpec(.leftParen, allowAtStartOfLine: false))
210+
shouldParseArgument = self.withLookahead {
211+
$0.atAttributeOrSpecifierArgument(lastTokenHadSpace: attributeNameHasTrailingSpace, forCustomAttribute: false)
212+
}
207213
case .noArgument:
208214
shouldParseArgument = false
209215
}
210216
if shouldParseArgument {
211217
var (unexpectedBeforeLeftParen, leftParen) = self.expect(TokenSpec(.leftParen, allowAtStartOfLine: false))
212-
if unexpectedBeforeLeftParen == nil
213-
&& (attributeName.raw.trailingTriviaByteLength > 0 || leftParen.leadingTriviaByteLength > 0)
214-
{
218+
219+
// Diagnose spaces between the name and the '('.
220+
if unexpectedBeforeLeftParen == nil && (attributeNameHasTrailingSpace || leftParen.leadingTriviaByteLength > 0) {
215221
let diagnostic = TokenDiagnostic(
216222
self.swiftVersion < .v6 ? .extraneousLeadingWhitespaceWarning : .extraneousLeadingWhitespaceError,
217223
byteOffset: 0
218224
)
219225
leftParen = leftParen.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
220226
}
227+
221228
let unexpectedBeforeArguments: RawUnexpectedNodesSyntax?
222229
let argument: RawAttributeSyntax.Arguments
223230
if let parseMissingArguments, leftParen.presence == .missing {
@@ -1074,44 +1081,70 @@ extension Parser {
10741081
// MARK: Lookahead
10751082

10761083
extension Parser.Lookahead {
1077-
mutating func atAttributeOrSpecifierArgument() -> Bool {
1084+
mutating func atAttributeOrSpecifierArgument(
1085+
lastTokenHadSpace: Bool,
1086+
forCustomAttribute: Bool = false
1087+
) -> Bool {
10781088
if !self.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) {
10791089
return false
10801090
}
10811091

1082-
var lookahead = self.lookahead()
1083-
lookahead.skipSingle()
1084-
1085-
// If we have any keyword, identifier, or token that follows a function
1086-
// type's parameter list, this is a parameter list and not an attribute.
1087-
// Alternatively, we might have a token that illustrates we're not going to
1088-
// get anything following the attribute, which means the parentheses describe
1089-
// what follows the attribute.
1090-
switch lookahead.currentToken {
1091-
case TokenSpec(.arrow),
1092-
TokenSpec(.throw),
1093-
TokenSpec(.throws),
1094-
TokenSpec(.rethrows),
1095-
TokenSpec(.rightParen),
1096-
TokenSpec(.rightBrace),
1097-
TokenSpec(.rightSquare),
1098-
TokenSpec(.rightAngle):
1099-
return false
1100-
case _ where lookahead.at(.keyword(.async)):
1101-
return false
1102-
case _ where lookahead.at(.keyword(.reasync)):
1103-
return false
1104-
default:
1105-
return true
1092+
if self.swiftVersion >= .v6 {
1093+
if !lastTokenHadSpace && currentToken.leadingTriviaByteLength == 0 {
1094+
return true
1095+
}
1096+
1097+
return withLookahead({
1098+
$0.skipSingle()
1099+
return $0.at(.atSign) || $0.atStartOfDeclaration()
1100+
})
1101+
} else {
1102+
if !forCustomAttribute {
1103+
return true
1104+
}
1105+
var lookahead = self.lookahead()
1106+
lookahead.skipSingle()
1107+
1108+
// If we have any keyword, identifier, or token that follows a function
1109+
// type's parameter list, this is a parameter list and not an attribute.
1110+
// Alternatively, we might have a token that illustrates we're not going to
1111+
// get anything following the attribute, which means the parentheses describe
1112+
// what follows the attribute.
1113+
switch lookahead.currentToken {
1114+
case TokenSpec(.arrow),
1115+
TokenSpec(.throw),
1116+
TokenSpec(.throws),
1117+
TokenSpec(.rethrows),
1118+
TokenSpec(.rightParen),
1119+
TokenSpec(.rightBrace),
1120+
TokenSpec(.rightSquare),
1121+
TokenSpec(.rightAngle):
1122+
return false
1123+
case _ where lookahead.at(.keyword(.async)):
1124+
return false
1125+
case _ where lookahead.at(.keyword(.reasync)):
1126+
return false
1127+
default:
1128+
return true
1129+
}
11061130
}
11071131
}
11081132

11091133
mutating func canParseCustomAttribute() -> Bool {
1110-
guard self.canParseType() else {
1134+
guard
1135+
let numTypeTokens = self.withLookahead({ $0.canParseSimpleType() ? $0.tokensConsumed : nil }),
1136+
numTypeTokens >= 1
1137+
else {
11111138
return false
11121139
}
1140+
// Check if the last token had trailing white spaces.
1141+
for _ in 0..<numTypeTokens - 1 {
1142+
self.consumeAnyToken()
1143+
}
1144+
let hasSpace = self.currentToken.trailingTriviaByteLength > 0
1145+
self.consumeAnyToken()
11131146

1114-
if self.withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
1147+
if self.atAttributeOrSpecifierArgument(lastTokenHadSpace: hasSpace, forCustomAttribute: true) {
11151148
self.skipSingle()
11161149
}
11171150

Sources/SwiftParser/Expressions.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,6 @@ extension TokenConsumer {
5151
case nil:
5252
break
5353
}
54-
if self.at(.atSign) || self.at(.keyword(.inout)) {
55-
var lookahead = self.lookahead()
56-
if lookahead.canParseType() {
57-
return true
58-
}
59-
}
6054
if self.at(.atSign) && self.peek(isAt: .stringQuote) {
6155
// Invalid Objective-C-style string literal
6256
return true

Sources/SwiftParser/Lookahead.swift

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -163,40 +163,31 @@ extension Parser.Lookahead {
163163
// MARK: Skipping Tokens
164164

165165
extension Parser.Lookahead {
166-
mutating func skipTypeAttribute() {
167-
// These are keywords that we accept as attribute names.
168-
guard self.at(.identifier) || self.at(.keyword(.in), .keyword(.inout)) else {
169-
return
170-
}
166+
/// Skip *any* single attribute. I.e. a type attribute, a decl attribute, or
167+
/// a custom attribute.
168+
mutating func consumeAnyAttribute() {
169+
self.eat(.atSign)
170+
171+
let nameHadSpace = self.currentToken.trailingTriviaByteLength > 0
171172

172173
// Determine which attribute it is.
173174
if let (attr, handle) = self.at(anyIn: TypeAttribute.self) {
174-
// Ok, it is a valid attribute, eat it, and then process it.
175175
self.eat(handle)
176176
switch attr {
177-
case .convention, .isolated:
178-
self.skipSingle()
177+
case .convention, .isolated, .differentiable:
178+
if self.atAttributeOrSpecifierArgument(lastTokenHadSpace: nameHadSpace) {
179+
self.skipSingle()
180+
}
179181
default:
180182
break
181183
}
182184
return
183185
}
184186

185187
if let (_, handle) = self.at(anyIn: Parser.DeclarationAttributeWithSpecialSyntax.self) {
186-
// This is a valid decl attribute so they should have put it on the decl
187-
// instead of the type.
188-
//
189-
// Recover by eating @foo(...)
190188
self.eat(handle)
191-
if self.at(.leftParen) {
192-
var lookahead = self.lookahead()
193-
lookahead.skipSingle()
194-
// If we found '->', or 'throws' after paren, it's likely a parameter
195-
// of function type.
196-
guard lookahead.at(.arrow) || lookahead.at(.keyword(.throws), .keyword(.rethrows), .keyword(.throw)) else {
197-
self.skipSingle()
198-
return
199-
}
189+
if self.atAttributeOrSpecifierArgument(lastTokenHadSpace: nameHadSpace) {
190+
self.skipSingle()
200191
}
201192
return
202193
}
@@ -212,18 +203,9 @@ extension Parser.Lookahead {
212203
return false
213204
}
214205

215-
while self.consume(if: .atSign) != nil {
216-
// Consume qualified names that may or may not involve generic arguments.
217-
repeat {
218-
self.consume(if: .identifier, .keyword(.rethrows))
219-
// We don't care whether this succeeds or fails to eat generic
220-
// parameters.
221-
_ = self.consumeGenericArguments()
222-
} while self.consume(if: .period) != nil
223-
224-
if self.atAttributeOrSpecifierArgument() {
225-
self.skipSingle()
226-
}
206+
var attributeProgress = LoopProgressCondition()
207+
while self.at(.atSign), self.hasProgressed(&attributeProgress) {
208+
self.consumeAnyAttribute()
227209
}
228210
return true
229211
}

Sources/SwiftParser/TopLevel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,10 @@ extension Parser {
255255
return self.parseStatementItem()
256256
} else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: true) {
257257
return .decl(self.parseDeclaration())
258+
} else if self.at(.atSign), peek(isAt: .identifier) {
259+
// Force parsing '@<identifier>' as a declaration, as there's no valid
260+
// expression or statement starting with an attribute.
261+
return .decl(self.parseDeclaration())
258262
} else {
259263
return .init(expr: RawMissingExprSyntax(arena: self.arena))
260264
}

Sources/SwiftParser/Types.swift

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ extension Parser.Lookahead {
790790

791791
case .keyword(.dependsOn):
792792
let canParseDependsOn = self.withLookahead({
793+
let nameHadSpace = $0.currentToken.trailingTriviaByteLength > 0
793794
// Consume 'dependsOn'
794795
$0.consumeAnyToken()
795796

@@ -798,7 +799,7 @@ extension Parser.Lookahead {
798799
}
799800

800801
// `dependsOn` requires an argument list.
801-
guard $0.atAttributeOrSpecifierArgument() else {
802+
guard $0.atAttributeOrSpecifierArgument(lastTokenHadSpace: nameHadSpace) else {
802803
return false
803804
}
804805

@@ -817,11 +818,7 @@ extension Parser.Lookahead {
817818
}
818819
}
819820

820-
var attributeProgress = LoopProgressCondition()
821-
while self.at(.atSign), self.hasProgressed(&attributeProgress) {
822-
self.consumeAnyToken()
823-
self.skipTypeAttribute()
824-
}
821+
_ = self.consumeAttributeList()
825822

826823
return true
827824
}
@@ -1186,7 +1183,9 @@ extension Parser {
11861183
// using `nonsisolated` without an argument is allowed in
11871184
// an inheritance clause.
11881185
// - The '(nonsending)' was omitted.
1189-
if !self.at(.leftParen) {
1186+
if !self.withLookahead({
1187+
$0.atAttributeOrSpecifierArgument(lastTokenHadSpace: nonisolatedKeyword.trailingTriviaByteLength > 0)
1188+
}) {
11901189
// `nonisolated P<...>` is allowed in an inheritance clause.
11911190
if withLookahead({ $0.canParseTypeIdentifier() }) {
11921191
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
@@ -1214,16 +1213,18 @@ extension Parser {
12141213
)
12151214

12161215
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
1217-
}
1216+
} else {
1217+
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
1218+
let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending))
1219+
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
12181220

1219-
// Avoid being to greedy about `(` since this modifier should be associated with
1220-
// function types, it's possible that the argument is omitted and what follows
1221-
// is a function type i.e. `nonisolated () async -> Void`.
1222-
if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
12231221
let argument = RawNonisolatedSpecifierArgumentSyntax(
1224-
leftParen: missingToken(.leftParen),
1225-
nonsendingKeyword: missingToken(.keyword(.nonsending)),
1226-
rightParen: missingToken(.rightParen),
1222+
unexpectedBeforeLeftParen,
1223+
leftParen: leftParen,
1224+
unexpectedBeforeModifier,
1225+
nonsendingKeyword: modifier,
1226+
unexpectedBeforeRightParen,
1227+
rightParen: rightParen,
12271228
arena: self.arena
12281229
)
12291230

@@ -1233,31 +1234,8 @@ extension Parser {
12331234
argument: argument,
12341235
arena: self.arena
12351236
)
1236-
12371237
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
12381238
}
1239-
1240-
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
1241-
let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending))
1242-
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
1243-
1244-
let argument = RawNonisolatedSpecifierArgumentSyntax(
1245-
unexpectedBeforeLeftParen,
1246-
leftParen: leftParen,
1247-
unexpectedBeforeModifier,
1248-
nonsendingKeyword: modifier,
1249-
unexpectedBeforeRightParen,
1250-
rightParen: rightParen,
1251-
arena: self.arena
1252-
)
1253-
1254-
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
1255-
unexpectedBeforeNonisolatedKeyword,
1256-
nonisolatedKeyword: nonisolatedKeyword,
1257-
argument: argument,
1258-
arena: self.arena
1259-
)
1260-
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
12611239
}
12621240

12631241
private mutating func parseSimpleTypeSpecifier(

0 commit comments

Comments
 (0)