Skip to content

Commit 9edef08

Browse files
committed
[SwiftParser] Lookahead: Teach skipTypeAttributeList about modifiers with arguments
1 parent b84e1ee commit 9edef08

File tree

2 files changed

+84
-10
lines changed

2 files changed

+84
-10
lines changed

Sources/SwiftParser/Types.swift

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -691,11 +691,27 @@ extension Parser.Lookahead {
691691
var specifierProgress = LoopProgressCondition()
692692
// TODO: Can we model isolated/_const so that they're specified in both canParse* and parse*?
693693
while canHaveParameterSpecifier,
694-
self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil || self.at(.keyword(.isolated))
695-
|| self.at(.keyword(._const)),
694+
self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil
695+
|| self.at(.keyword(.nonisolated), .keyword(.dependsOn)),
696696
self.hasProgressed(&specifierProgress)
697697
{
698-
self.consumeAnyToken()
698+
switch self.currentToken {
699+
case .keyword(.nonisolated), .keyword(.dependsOn):
700+
self.consumeAnyToken()
701+
702+
// The argument is missing but it still could be a valid modifier,
703+
// i.e. `nonisolated` in an inheritance clause.
704+
guard self.at(.leftParen) else {
705+
continue
706+
}
707+
708+
if self.withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
709+
skipSingle()
710+
}
711+
712+
default:
713+
self.consumeAnyToken()
714+
}
699715
}
700716

701717
var attributeProgress = LoopProgressCondition()
@@ -1059,10 +1075,24 @@ extension Parser {
10591075
private mutating func parseNonisolatedTypeSpecifier() -> RawTypeSpecifierListSyntax.Element {
10601076
let (unexpectedBeforeNonisolatedKeyword, nonisolatedKeyword) = self.expect(.keyword(.nonisolated))
10611077

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() }) {
1078+
// If the next token is not '(' this could mean two things:
1079+
// - What follows is a type and we should allow it because
1080+
// using `nonsisolated` without an argument is allowed in
1081+
// an inheritance clause.
1082+
// - The '(nonsending)' was omitted.
1083+
if !self.at(.leftParen) {
1084+
// `nonisolated P<...>` is allowed in an inheritance clause.
1085+
if withLookahead({ $0.canParseTypeIdentifier() }) {
1086+
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
1087+
unexpectedBeforeNonisolatedKeyword,
1088+
nonisolatedKeyword: nonisolatedKeyword,
1089+
argument: nil,
1090+
arena: self.arena
1091+
)
1092+
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
1093+
}
1094+
1095+
// Otherwise require '(nonsending)'
10661096
let argument = RawNonisolatedSpecifierArgumentSyntax(
10671097
leftParen: missingToken(.leftParen),
10681098
nonsendingKeyword: missingToken(.keyword(.nonsending)),
@@ -1076,24 +1106,37 @@ extension Parser {
10761106
argument: argument,
10771107
arena: self.arena
10781108
)
1109+
10791110
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
10801111
}
10811112

1082-
// nonisolated without an argument is valid in some positions i.e. inheritance clause.
1083-
guard let leftParen = self.consume(if: .leftParen) else {
1113+
// Avoid being to greedy about `(` since this modifier should be associated with
1114+
// function types, it's possible that the argument is omitted and what follows
1115+
// is a function type i.e. `nonisolated () async -> Void`.
1116+
if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
1117+
let argument = RawNonisolatedSpecifierArgumentSyntax(
1118+
leftParen: missingToken(.leftParen),
1119+
nonsendingKeyword: missingToken(.keyword(.nonsending)),
1120+
rightParen: missingToken(.rightParen),
1121+
arena: self.arena
1122+
)
1123+
10841124
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
10851125
unexpectedBeforeNonisolatedKeyword,
10861126
nonisolatedKeyword: nonisolatedKeyword,
1087-
argument: nil,
1127+
argument: argument,
10881128
arena: self.arena
10891129
)
1130+
10901131
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
10911132
}
10921133

1134+
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
10931135
let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending))
10941136
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
10951137

10961138
let argument = RawNonisolatedSpecifierArgumentSyntax(
1139+
unexpectedBeforeLeftParen,
10971140
leftParen: leftParen,
10981141
unexpectedBeforeModifier,
10991142
nonsendingKeyword: modifier,

Tests/SwiftParserTest/TypeTests.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,20 @@ final class TypeTests: ParserTestCase {
485485
experimentalFeatures: [.nonescapableTypes]
486486
)
487487

488+
assertParse(
489+
"func foo() -> dependsOn1️⃣ @Sendable (Int, isolated (any Actor)?) async throws -> Void",
490+
diagnostics: [
491+
DiagnosticSpec(
492+
locationMarker: "1️⃣",
493+
message: "expected '(', parameter reference, and ')' in lifetime specifier",
494+
fixIts: ["insert '(', parameter reference, and ')'"]
495+
)
496+
],
497+
fixedSource:
498+
"func foo() -> dependsOn(<#identifier#>) @Sendable (Int, isolated (any Actor)?) async throws -> Void",
499+
experimentalFeatures: [.nonescapableTypes]
500+
)
501+
488502
assertParse(
489503
"func foo() -> dependsOn(1️⃣*) X",
490504
diagnostics: [
@@ -545,6 +559,23 @@ final class TypeTests: ParserTestCase {
545559
assertParse("let _ = Array<nonisolated(nonsending) () async -> Void>()")
546560
assertParse("func foo(test: nonisolated(nonsending) () async -> Void)")
547561
assertParse("func foo(test: nonisolated(nonsending) @escaping () async -> Void) {}")
562+
assertParse("test(S<nonisolated(nonsending) () async -> Void>(), type(of: concurrentTest))")
563+
assertParse("S<nonisolated(nonsending) @Sendable (Int) async -> Void>()")
564+
assertParse("let _ = S<nonisolated(nonsending) consuming @Sendable (Int) async -> Void>()")
565+
assertParse("struct S : nonisolated P {}")
566+
assertParse("let _ = [nonisolated()]")
567+
568+
assertParse(
569+
"Foo<Int, nonisolated1️⃣ @Sendable (Int, inout (any Actor)?) async throws -> Void>()",
570+
diagnostics: [
571+
DiagnosticSpec(
572+
locationMarker: "1️⃣",
573+
message: "expected '(nonsending)' in 'nonisolated' specifier",
574+
fixIts: ["insert '(nonsending)'"]
575+
)
576+
],
577+
fixedSource: "Foo<Int, nonisolated(nonsending) @Sendable (Int, inout (any Actor)?) async throws -> Void>()"
578+
)
548579

549580
assertParse(
550581
"func foo(test: nonisolated1️⃣ () async -> Void)",

0 commit comments

Comments
 (0)