Skip to content

[SwiftParser] Implement nonisolated(nonsending) specifier #3047

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 12, 2025
Merged
3 changes: 3 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ public enum Keyword: CaseIterable {
case none
case nonisolated
case nonmutating
case nonsending
case objc
case obsoleted
case of
Expand Down Expand Up @@ -551,6 +552,8 @@ public enum Keyword: CaseIterable {
return KeywordSpec("nonisolated")
case .nonmutating:
return KeywordSpec("nonmutating")
case .nonsending:
return KeywordSpec("nonsending")
case .objc:
return KeywordSpec("objc")
case .obsoleted:
Expand Down
3 changes: 3 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
case multipleTrailingClosureElementList
case namedOpaqueReturnType
case nilLiteralExpr
case nonisolatedSpecifierArgument
case nonisolatedSpecifierArgumentList
case nonisolatedTypeSpecifier
case objCSelectorPiece
case objCSelectorPieceList
case operatorDecl
Expand Down
49 changes: 47 additions & 2 deletions CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,52 @@ public let TYPE_NODES: [Node] = [
]
),

Node(
kind: .nonisolatedSpecifierArgument,
base: .syntax,
nameForDiagnostics: nil,
documentation: """
A single argument that can be added to a nonisolated specifier: 'nonsending'.

### Example
`data` in `func foo(data: nonisolated(nonsending) () async -> Void) -> X`
""",
traits: [
"Parenthesized"
],
children: [
Child(
name: "leftParen",
kind: .token(choices: [.token(.leftParen)])
),
Child(
name: "nonsendingKeyword",
kind: .token(choices: [.keyword(.nonsending)])
),
Child(
name: "rightParen",
kind: .token(choices: [.token(.rightParen)])
),
]
),

Node(
kind: .nonisolatedTypeSpecifier,
base: .syntax,
nameForDiagnostics: "'nonisolated' specifier",
children: [
Child(
name: "nonisolatedKeyword",
kind: .token(choices: [.keyword(.nonisolated)])
),
Child(
name: "argument",
kind: .node(kind: .nonisolatedSpecifierArgument),
isOptional: true
),
]
),

Node(
kind: .simpleTypeSpecifier,
base: .syntax,
Expand All @@ -689,7 +735,6 @@ public let TYPE_NODES: [Node] = [
.keyword(.__shared),
.keyword(.__owned),
.keyword(.isolated),
.keyword(.nonisolated),
.keyword(._const),
.keyword(.borrowing),
.keyword(.consuming),
Expand All @@ -704,6 +749,6 @@ public let TYPE_NODES: [Node] = [
kind: .typeSpecifierList,
base: .syntaxCollection,
nameForDiagnostics: nil,
elementChoices: [.simpleTypeSpecifier, .lifetimeTypeSpecifier]
elementChoices: [.simpleTypeSpecifier, .lifetimeTypeSpecifier, .nonisolatedTypeSpecifier]
),
]
6 changes: 3 additions & 3 deletions Sources/SwiftParser/Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ extension Parser {
shouldParseArgument = true
case .customAttribute:
shouldParseArgument =
self.withLookahead { $0.atCustomAttributeArgument() }
self.withLookahead { $0.atAttributeOrSpecifierArgument() }
&& self.at(TokenSpec(.leftParen, allowAtStartOfLine: false))
case .optional:
shouldParseArgument = self.at(.leftParen)
Expand Down Expand Up @@ -1002,7 +1002,7 @@ extension Parser {
// MARK: Lookahead

extension Parser.Lookahead {
mutating func atCustomAttributeArgument() -> Bool {
mutating func atAttributeOrSpecifierArgument() -> Bool {
var lookahead = self.lookahead()
lookahead.skipSingle()

Expand Down Expand Up @@ -1036,7 +1036,7 @@ extension Parser.Lookahead {
}

if self.at(TokenSpec(.leftParen, allowAtStartOfLine: false))
&& self.withLookahead({ $0.atCustomAttributeArgument() })
&& self.withLookahead({ $0.atAttributeOrSpecifierArgument() })
{
self.skipSingle()
}
Expand Down
6 changes: 5 additions & 1 deletion Sources/SwiftParser/Modifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,11 @@ extension Parser {
let detail: RawDeclModifierDetailSyntax?
if self.at(.leftParen) {
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
let (unexpectedBeforeDetailToken, detailToken) = self.expect(TokenSpec(.unsafe, remapping: .identifier))
let (unexpectedBeforeDetailToken, detailToken) = self.expect(
TokenSpec(.unsafe, remapping: .identifier),
TokenSpec(.nonsending, remapping: .identifier),
default: TokenSpec(.identifier)
)
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
detail = RawDeclModifierDetailSyntax(
unexpectedBeforeLeftParen,
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftParser/TokenPrecedence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ enum TokenPrecedence: Comparable {
.module,
.noasync,
.none,
.nonsending,
.obsoleted,
.of,
.Protocol,
Expand Down
112 changes: 108 additions & 4 deletions Sources/SwiftParser/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -689,13 +689,28 @@ extension Parser.Lookahead {

mutating func skipTypeAttributeList() {
var specifierProgress = LoopProgressCondition()
// TODO: Can we model isolated/_const so that they're specified in both canParse* and parse*?
while canHaveParameterSpecifier,
self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil || self.at(.keyword(.isolated))
|| self.at(.keyword(._const)),
self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil
|| self.at(.keyword(.nonisolated), .keyword(.dependsOn)),
self.hasProgressed(&specifierProgress)
{
self.consumeAnyToken()
switch self.currentToken {
case .keyword(.nonisolated), .keyword(.dependsOn):
self.consumeAnyToken()

// The argument is missing but it still could be a valid modifier,
// i.e. `nonisolated` in an inheritance clause.
guard self.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) else {
continue
}

if self.withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
skipSingle()
}

default:
self.consumeAnyToken()
}
}

var attributeProgress = LoopProgressCondition()
Expand Down Expand Up @@ -1056,6 +1071,88 @@ extension Parser {
return .lifetimeTypeSpecifier(lifetimeSpecifier)
}

private mutating func parseNonisolatedTypeSpecifier() -> RawTypeSpecifierListSyntax.Element {
let (unexpectedBeforeNonisolatedKeyword, nonisolatedKeyword) = self.expect(.keyword(.nonisolated))

// If the next token is not '(' this could mean two things:
// - What follows is a type and we should allow it because
// using `nonsisolated` without an argument is allowed in
// an inheritance clause.
// - The '(nonsending)' was omitted.
if !self.at(.leftParen) {
// `nonisolated P<...>` is allowed in an inheritance clause.
if withLookahead({ $0.canParseTypeIdentifier() }) {
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
unexpectedBeforeNonisolatedKeyword,
nonisolatedKeyword: nonisolatedKeyword,
argument: nil,
arena: self.arena
)
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
}

// Otherwise require '(nonsending)'
let argument = RawNonisolatedSpecifierArgumentSyntax(
leftParen: missingToken(.leftParen),
nonsendingKeyword: missingToken(.keyword(.nonsending)),
rightParen: missingToken(.rightParen),
arena: self.arena
)

let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
unexpectedBeforeNonisolatedKeyword,
nonisolatedKeyword: nonisolatedKeyword,
argument: argument,
arena: self.arena
)

return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
}

// Avoid being to greedy about `(` since this modifier should be associated with
// function types, it's possible that the argument is omitted and what follows
// is a function type i.e. `nonisolated () async -> Void`.
if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
let argument = RawNonisolatedSpecifierArgumentSyntax(
leftParen: missingToken(.leftParen),
nonsendingKeyword: missingToken(.keyword(.nonsending)),
rightParen: missingToken(.rightParen),
arena: self.arena
)

let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
unexpectedBeforeNonisolatedKeyword,
nonisolatedKeyword: nonisolatedKeyword,
argument: argument,
arena: self.arena
)

return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
}

let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending))
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)

let argument = RawNonisolatedSpecifierArgumentSyntax(
unexpectedBeforeLeftParen,
leftParen: leftParen,
unexpectedBeforeModifier,
nonsendingKeyword: modifier,
unexpectedBeforeRightParen,
rightParen: rightParen,
arena: self.arena
)

let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
unexpectedBeforeNonisolatedKeyword,
nonisolatedKeyword: nonisolatedKeyword,
argument: argument,
arena: self.arena
)
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
}

private mutating func parseSimpleTypeSpecifier(
specifierHandle: TokenConsumptionHandle
) -> RawTypeSpecifierListSyntax.Element {
Expand All @@ -1079,6 +1176,13 @@ extension Parser {
} else {
break SPECIFIER_PARSING
}
} else if self.at(.keyword(.nonisolated)) {
// If '(' is located on the new line 'nonisolated' cannot be parsed
// as a specifier.
if self.peek(isAt: .leftParen) && self.peek().isAtStartOfLine {
break SPECIFIER_PARSING
}
specifiers.append(parseNonisolatedTypeSpecifier())
} else {
break SPECIFIER_PARSING
}
Expand Down
9 changes: 0 additions & 9 deletions Sources/SwiftParser/generated/Parser+TokenSpecSet.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Sources/SwiftParserDiagnostics/SyntaxExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ extension TypeSpecifierListSyntax {
switch specifier {
case .simpleTypeSpecifier(let specifier): return specifier.specifier
case .lifetimeTypeSpecifier: return nil
case .nonisolatedTypeSpecifier: return nil
#if RESILIENT_LIBRARIES
@unknown default:
fatalError()
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading