Skip to content

Commit fb65fcb

Browse files
authored
Merge pull request #3047 from xedin/nonisolated-nonsending-attr
[SwiftParser] Implement `nonisolated(nonsending)` specifier
2 parents cf4e08b + 2ae464e commit fb65fcb

30 files changed

+1125
-33
lines changed

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

+3
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ public enum Keyword: CaseIterable {
210210
case none
211211
case nonisolated
212212
case nonmutating
213+
case nonsending
213214
case objc
214215
case obsoleted
215216
case of
@@ -551,6 +552,8 @@ public enum Keyword: CaseIterable {
551552
return KeywordSpec("nonisolated")
552553
case .nonmutating:
553554
return KeywordSpec("nonmutating")
555+
case .nonsending:
556+
return KeywordSpec("nonsending")
554557
case .objc:
555558
return KeywordSpec("objc")
556559
case .obsoleted:

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

+3
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
210210
case multipleTrailingClosureElementList
211211
case namedOpaqueReturnType
212212
case nilLiteralExpr
213+
case nonisolatedSpecifierArgument
214+
case nonisolatedSpecifierArgumentList
215+
case nonisolatedTypeSpecifier
213216
case objCSelectorPiece
214217
case objCSelectorPieceList
215218
case operatorDecl

CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift

+47-2
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,52 @@ public let TYPE_NODES: [Node] = [
676676
]
677677
),
678678

679+
Node(
680+
kind: .nonisolatedSpecifierArgument,
681+
base: .syntax,
682+
nameForDiagnostics: nil,
683+
documentation: """
684+
A single argument that can be added to a nonisolated specifier: 'nonsending'.
685+
686+
### Example
687+
`data` in `func foo(data: nonisolated(nonsending) () async -> Void) -> X`
688+
""",
689+
traits: [
690+
"Parenthesized"
691+
],
692+
children: [
693+
Child(
694+
name: "leftParen",
695+
kind: .token(choices: [.token(.leftParen)])
696+
),
697+
Child(
698+
name: "nonsendingKeyword",
699+
kind: .token(choices: [.keyword(.nonsending)])
700+
),
701+
Child(
702+
name: "rightParen",
703+
kind: .token(choices: [.token(.rightParen)])
704+
),
705+
]
706+
),
707+
708+
Node(
709+
kind: .nonisolatedTypeSpecifier,
710+
base: .syntax,
711+
nameForDiagnostics: "'nonisolated' specifier",
712+
children: [
713+
Child(
714+
name: "nonisolatedKeyword",
715+
kind: .token(choices: [.keyword(.nonisolated)])
716+
),
717+
Child(
718+
name: "argument",
719+
kind: .node(kind: .nonisolatedSpecifierArgument),
720+
isOptional: true
721+
),
722+
]
723+
),
724+
679725
Node(
680726
kind: .simpleTypeSpecifier,
681727
base: .syntax,
@@ -689,7 +735,6 @@ public let TYPE_NODES: [Node] = [
689735
.keyword(.__shared),
690736
.keyword(.__owned),
691737
.keyword(.isolated),
692-
.keyword(.nonisolated),
693738
.keyword(._const),
694739
.keyword(.borrowing),
695740
.keyword(.consuming),
@@ -704,6 +749,6 @@ public let TYPE_NODES: [Node] = [
704749
kind: .typeSpecifierList,
705750
base: .syntaxCollection,
706751
nameForDiagnostics: nil,
707-
elementChoices: [.simpleTypeSpecifier, .lifetimeTypeSpecifier]
752+
elementChoices: [.simpleTypeSpecifier, .lifetimeTypeSpecifier, .nonisolatedTypeSpecifier]
708753
),
709754
]

Sources/SwiftParser/Attributes.swift

+3-3
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/Expressions.swift

+28-2
Original file line numberDiff line numberDiff line change
@@ -381,13 +381,39 @@ extension Parser {
381381
}
382382
}
383383

384+
/// Make sure that we only accept `nonisolated(nonsending)` as a valid type specifier
385+
/// in expression context to minimize source compatibility impact.
386+
func canParseNonisolatedAsSpecifierInExpressionContext() -> Bool {
387+
return withLookahead {
388+
guard $0.consume(if: .keyword(.nonisolated)) != nil else {
389+
return false
390+
}
391+
392+
if $0.currentToken.isAtStartOfLine {
393+
return false
394+
}
395+
396+
guard $0.consume(if: .leftParen) != nil else {
397+
return false
398+
}
399+
400+
guard $0.consume(if: TokenSpec(.nonsending, allowAtStartOfLine: false)) != nil else {
401+
return false
402+
}
403+
404+
return $0.at(TokenSpec(.rightParen, allowAtStartOfLine: false))
405+
}
406+
}
407+
384408
/// Parse an expression sequence element.
385409
mutating func parseSequenceExpressionElement(
386410
flavor: ExprFlavor,
387411
pattern: PatternContext = .none
388412
) -> RawExprSyntax {
389-
// Try to parse '@' sign or 'inout' as an attributed typerepr.
390-
if self.at(.atSign, .keyword(.inout)) {
413+
// Try to parse '@' sign, 'inout', or 'nonisolated' as an attributed typerepr.
414+
if self.at(.atSign, .keyword(.inout))
415+
|| self.canParseNonisolatedAsSpecifierInExpressionContext()
416+
{
391417
var lookahead = self.lookahead()
392418
if lookahead.canParseType() {
393419
let type = self.parseType()

Sources/SwiftParser/Modifiers.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,11 @@ extension Parser {
236236
let detail: RawDeclModifierDetailSyntax?
237237
if self.at(.leftParen) {
238238
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
239-
let (unexpectedBeforeDetailToken, detailToken) = self.expect(TokenSpec(.unsafe, remapping: .identifier))
239+
let (unexpectedBeforeDetailToken, detailToken) = self.expect(
240+
TokenSpec(.unsafe, remapping: .identifier),
241+
TokenSpec(.nonsending, remapping: .identifier),
242+
default: TokenSpec(.identifier)
243+
)
240244
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
241245
detail = RawDeclModifierDetailSyntax(
242246
unexpectedBeforeLeftParen,

Sources/SwiftParser/TokenPrecedence.swift

+1
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ enum TokenPrecedence: Comparable {
326326
.module,
327327
.noasync,
328328
.none,
329+
.nonsending,
329330
.obsoleted,
330331
.of,
331332
.Protocol,

Sources/SwiftParser/Types.swift

+161-6
Original file line numberDiff line numberDiff line change
@@ -687,26 +687,92 @@ extension Parser.Lookahead {
687687
return true
688688
}
689689

690-
mutating func skipTypeAttributeList() {
690+
mutating func canParseTypeAttributeList() -> Bool {
691691
var specifierProgress = LoopProgressCondition()
692-
// TODO: Can we model isolated/_const so that they're specified in both canParse* and parse*?
693692
while canHaveParameterSpecifier,
694-
self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil || self.at(.keyword(.isolated))
695-
|| self.at(.keyword(._const)),
693+
self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil
694+
|| self.at(.keyword(.nonisolated), .keyword(.dependsOn)),
696695
self.hasProgressed(&specifierProgress)
697696
{
698-
self.consumeAnyToken()
697+
switch self.currentToken {
698+
case .keyword(.nonisolated):
699+
let canParseNonisolated = self.withLookahead({
700+
// Consume 'nonisolated'
701+
$0.consumeAnyToken()
702+
703+
// The argument is missing but it still could be a valid modifier,
704+
// i.e. `nonisolated` in an inheritance clause.
705+
guard $0.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) else {
706+
return true
707+
}
708+
709+
// Consume '('
710+
$0.consumeAnyToken()
711+
712+
// nonisolated accepts a single modifier at the moment: 'nonsending'
713+
// we need to check for that explicitly to avoid misinterpreting this
714+
// keyword to be a modifier when it isn't i.e. `[nonisolated(42)]`
715+
guard $0.consume(if: TokenSpec(.nonsending, allowAtStartOfLine: false)) != nil else {
716+
return false
717+
}
718+
719+
return $0.consume(if: TokenSpec(.rightParen, allowAtStartOfLine: false)) != nil
720+
})
721+
722+
guard canParseNonisolated else {
723+
return false
724+
}
725+
726+
self.consumeAnyToken()
727+
728+
guard self.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) else {
729+
continue
730+
}
731+
732+
self.skipSingle()
733+
734+
case .keyword(.dependsOn):
735+
let canParseDependsOn = self.withLookahead({
736+
// Consume 'dependsOn'
737+
$0.consumeAnyToken()
738+
739+
if $0.currentToken.isAtStartOfLine {
740+
return false
741+
}
742+
743+
// `dependsOn` requires an argument list.
744+
guard $0.atAttributeOrSpecifierArgument() else {
745+
return false
746+
}
747+
748+
return true
749+
})
750+
751+
guard canParseDependsOn else {
752+
return false
753+
}
754+
755+
self.consumeAnyToken()
756+
self.skipSingle()
757+
758+
default:
759+
self.consumeAnyToken()
760+
}
699761
}
700762

701763
var attributeProgress = LoopProgressCondition()
702764
while self.at(.atSign), self.hasProgressed(&attributeProgress) {
703765
self.consumeAnyToken()
704766
self.skipTypeAttribute()
705767
}
768+
769+
return true
706770
}
707771

708772
mutating func canParseTypeScalar() -> Bool {
709-
self.skipTypeAttributeList()
773+
guard self.canParseTypeAttributeList() else {
774+
return false
775+
}
710776

711777
guard self.canParseSimpleOrCompositionType() else {
712778
return false
@@ -1056,6 +1122,88 @@ extension Parser {
10561122
return .lifetimeTypeSpecifier(lifetimeSpecifier)
10571123
}
10581124

1125+
private mutating func parseNonisolatedTypeSpecifier() -> RawTypeSpecifierListSyntax.Element {
1126+
let (unexpectedBeforeNonisolatedKeyword, nonisolatedKeyword) = self.expect(.keyword(.nonisolated))
1127+
1128+
// If the next token is not '(' this could mean two things:
1129+
// - What follows is a type and we should allow it because
1130+
// using `nonsisolated` without an argument is allowed in
1131+
// an inheritance clause.
1132+
// - The '(nonsending)' was omitted.
1133+
if !self.at(.leftParen) {
1134+
// `nonisolated P<...>` is allowed in an inheritance clause.
1135+
if withLookahead({ $0.canParseTypeIdentifier() }) {
1136+
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
1137+
unexpectedBeforeNonisolatedKeyword,
1138+
nonisolatedKeyword: nonisolatedKeyword,
1139+
argument: nil,
1140+
arena: self.arena
1141+
)
1142+
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
1143+
}
1144+
1145+
// Otherwise require '(nonsending)'
1146+
let argument = RawNonisolatedSpecifierArgumentSyntax(
1147+
leftParen: missingToken(.leftParen),
1148+
nonsendingKeyword: missingToken(.keyword(.nonsending)),
1149+
rightParen: missingToken(.rightParen),
1150+
arena: self.arena
1151+
)
1152+
1153+
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
1154+
unexpectedBeforeNonisolatedKeyword,
1155+
nonisolatedKeyword: nonisolatedKeyword,
1156+
argument: argument,
1157+
arena: self.arena
1158+
)
1159+
1160+
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
1161+
}
1162+
1163+
// Avoid being to greedy about `(` since this modifier should be associated with
1164+
// function types, it's possible that the argument is omitted and what follows
1165+
// is a function type i.e. `nonisolated () async -> Void`.
1166+
if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
1167+
let argument = RawNonisolatedSpecifierArgumentSyntax(
1168+
leftParen: missingToken(.leftParen),
1169+
nonsendingKeyword: missingToken(.keyword(.nonsending)),
1170+
rightParen: missingToken(.rightParen),
1171+
arena: self.arena
1172+
)
1173+
1174+
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
1175+
unexpectedBeforeNonisolatedKeyword,
1176+
nonisolatedKeyword: nonisolatedKeyword,
1177+
argument: argument,
1178+
arena: self.arena
1179+
)
1180+
1181+
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
1182+
}
1183+
1184+
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
1185+
let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending))
1186+
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
1187+
1188+
let argument = RawNonisolatedSpecifierArgumentSyntax(
1189+
unexpectedBeforeLeftParen,
1190+
leftParen: leftParen,
1191+
unexpectedBeforeModifier,
1192+
nonsendingKeyword: modifier,
1193+
unexpectedBeforeRightParen,
1194+
rightParen: rightParen,
1195+
arena: self.arena
1196+
)
1197+
1198+
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
1199+
unexpectedBeforeNonisolatedKeyword,
1200+
nonisolatedKeyword: nonisolatedKeyword,
1201+
argument: argument,
1202+
arena: self.arena
1203+
)
1204+
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
1205+
}
1206+
10591207
private mutating func parseSimpleTypeSpecifier(
10601208
specifierHandle: TokenConsumptionHandle
10611209
) -> RawTypeSpecifierListSyntax.Element {
@@ -1079,6 +1227,13 @@ extension Parser {
10791227
} else {
10801228
break SPECIFIER_PARSING
10811229
}
1230+
} else if self.at(.keyword(.nonisolated)) {
1231+
// If '(' is located on the new line 'nonisolated' cannot be parsed
1232+
// as a specifier.
1233+
if self.peek(isAt: .leftParen) && self.peek().isAtStartOfLine {
1234+
break SPECIFIER_PARSING
1235+
}
1236+
specifiers.append(parseNonisolatedTypeSpecifier())
10821237
} else {
10831238
break SPECIFIER_PARSING
10841239
}

0 commit comments

Comments
 (0)