Skip to content

Commit 3977abe

Browse files
committed
[SwiftParser] Make nonisolated(nonsending) parsing check before consuming '('
It's possible that `(nonsending)` was omitted and instead we are at the parameter list of a function type. Checking ahead allows for a better diagnostics and recovery.
1 parent a7f65cd commit 3977abe

File tree

3 files changed

+38
-3
lines changed

3 files changed

+38
-3
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ extension Parser {
157157
shouldParseArgument = true
158158
case .customAttribute:
159159
shouldParseArgument =
160-
self.withLookahead { $0.atCustomAttributeArgument() }
160+
self.withLookahead { $0.atAttributeOrSpecifierArgument() }
161161
&& self.at(TokenSpec(.leftParen, allowAtStartOfLine: false))
162162
case .optional:
163163
shouldParseArgument = self.at(.leftParen)
@@ -1002,7 +1002,7 @@ extension Parser {
10021002
// MARK: Lookahead
10031003

10041004
extension Parser.Lookahead {
1005-
mutating func atCustomAttributeArgument() -> Bool {
1005+
mutating func atAttributeOrSpecifierArgument() -> Bool {
10061006
var lookahead = self.lookahead()
10071007
lookahead.skipSingle()
10081008

@@ -1036,7 +1036,7 @@ extension Parser.Lookahead {
10361036
}
10371037

10381038
if self.at(TokenSpec(.leftParen, allowAtStartOfLine: false))
1039-
&& self.withLookahead({ $0.atCustomAttributeArgument() })
1039+
&& self.withLookahead({ $0.atAttributeOrSpecifierArgument() })
10401040
{
10411041
self.skipSingle()
10421042
}

Sources/SwiftParser/Types.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,27 @@ extension Parser {
10591059
private mutating func parseNonisolatedTypeSpecifier() -> RawTypeSpecifierListSyntax.Element {
10601060
let (unexpectedBeforeNonisolatedKeyword, nonisolatedKeyword) = self.expect(.keyword(.nonisolated))
10611061

1062+
// Avoid being to greedy about `(` since this modifier should be associated with
1063+
// function types, it's possible that the argument is omitted and what follows
1064+
// is a function type i.e. `nonisolated () async -> Void`.
1065+
if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
1066+
let argument = RawNonisolatedSpecifierArgumentSyntax(
1067+
leftParen: missingToken(.leftParen),
1068+
nonsendingKeyword: missingToken(.keyword(.nonsending)),
1069+
rightParen: missingToken(.rightParen),
1070+
arena: self.arena
1071+
)
1072+
1073+
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
1074+
unexpectedBeforeNonisolatedKeyword,
1075+
nonisolatedKeyword: nonisolatedKeyword,
1076+
argument: argument,
1077+
arena: self.arena
1078+
)
1079+
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
1080+
}
1081+
1082+
// nonisolated without an argument is valid in some positions i.e. inheritance clause.
10621083
guard let leftParen = self.consume(if: .leftParen) else {
10631084
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
10641085
unexpectedBeforeNonisolatedKeyword,

Tests/SwiftParserTest/TypeTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,23 @@ final class TypeTests: ParserTestCase {
546546
assertParse("func foo(test: nonisolated(nonsending) () async -> Void)")
547547
assertParse("func foo(test: nonisolated(nonsending) @escaping () async -> Void) {}")
548548

549+
assertParse(
550+
"func foo(test: nonisolated1️⃣ () async -> Void)",
551+
diagnostics: [
552+
DiagnosticSpec(
553+
locationMarker: "1️⃣",
554+
message: "expected '(nonsending)' in 'nonisolated' specifier",
555+
fixIts: ["insert '(nonsending)'"]
556+
)
557+
],
558+
fixedSource: "func foo(test: nonisolated(nonsending) () async -> Void)"
559+
)
560+
549561
assertParse(
550562
"func foo(test: nonisolated(1️⃣) () async -> Void)",
551563
diagnostics: [
552564
DiagnosticSpec(
565+
locationMarker: "1️⃣",
553566
message: "expected 'nonsending' in 'nonisolated' specifier",
554567
fixIts: ["insert 'nonsending'"]
555568
)
@@ -561,6 +574,7 @@ final class TypeTests: ParserTestCase {
561574
"func foo(test: nonisolated(1️⃣hello) () async -> Void)",
562575
diagnostics: [
563576
DiagnosticSpec(
577+
locationMarker: "1️⃣",
564578
message: "expected 'nonsending' in 'nonisolated' specifier",
565579
fixIts: ["insert 'nonsending'"]
566580
),

0 commit comments

Comments
 (0)