Skip to content

Commit 4d4e241

Browse files
committed
Add representation and parser support for the @isolated type attribute
As part of this, I had to update the attribute parser to not recurse into the full parseType routine since that would attempt to parse attributes again right after the `@`. Doing so allows some genuinely bizarre constructs that we obviously don't want and will reject in semantic analysis anyway. For `@isolated` specifically, it creates an artificial parsing conflict with the `isolated` parameter specifier and possible future declaration modifier, just in case you wanted attributes to apply to your attributes.
1 parent 2fd65c2 commit 4d4e241

File tree

6 files changed

+106
-11
lines changed

6 files changed

+106
-11
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ extension Parser {
177177
parseArguments: (inout Parser) -> RawAttributeSyntax.Arguments
178178
) -> RawAttributeListSyntax.Element {
179179
let (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
180-
let attributeName = self.parseType()
180+
let attributeName = self.parseAttributeName()
181181
let shouldParseArgument: Bool
182182
switch argumentMode {
183183
case .required:
@@ -358,21 +358,40 @@ extension Parser {
358358
}
359359
}
360360

361-
extension Parser {
362-
mutating func parseMacroRoleArguments() -> [RawLabeledExprSyntax] {
363-
let (unexpectedBeforeRole, role) = self.expect(.identifier, TokenSpec(.extension, remapping: .identifier), default: .identifier)
364-
let roleTrailingComma = self.consume(if: .comma)
365-
let roleElement = RawLabeledExprSyntax(
361+
extension RawLabeledExprSyntax {
362+
fileprivate init(
363+
_ unexpectedBeforeIdentifier: RawUnexpectedNodesSyntax? = nil,
364+
identifier: RawTokenSyntax,
365+
_ unexpectedBetweenIdentifierAndTrailingComma: RawUnexpectedNodesSyntax? = nil,
366+
trailingComma: RawTokenSyntax? = nil,
367+
arena: __shared SyntaxArena
368+
) {
369+
self.init(
366370
label: nil,
367371
colon: nil,
368372
expression: RawExprSyntax(
369373
RawDeclReferenceExprSyntax(
370-
unexpectedBeforeRole,
371-
baseName: role,
374+
unexpectedBeforeIdentifier,
375+
baseName: identifier,
372376
argumentNames: nil,
373-
arena: self.arena
377+
arena: arena
374378
)
375379
),
380+
unexpectedBetweenIdentifierAndTrailingComma,
381+
trailingComma: trailingComma,
382+
arena: arena
383+
)
384+
}
385+
}
386+
387+
extension Parser {
388+
mutating func parseMacroRoleArguments() -> [RawLabeledExprSyntax] {
389+
let (unexpectedBeforeRole, role) = self.expect(.identifier, TokenSpec(.extension, remapping: .identifier), default: .identifier)
390+
let roleTrailingComma = self.consume(if: .comma)
391+
392+
let roleElement = RawLabeledExprSyntax(
393+
unexpectedBeforeRole,
394+
identifier: role,
376395
trailingComma: roleTrailingComma,
377396
arena: self.arena
378397
)
@@ -848,6 +867,23 @@ extension Parser {
848867
}
849868
}
850869

870+
extension Parser {
871+
mutating func parseIsolatedAttributeArguments() -> RawLabeledExprListSyntax {
872+
let (unexpectedBeforeIsolationKind, isolationKind) =
873+
self.expectIdentifier(allowKeywordsAsIdentifier: true)
874+
let isolationKindElement = RawLabeledExprSyntax(
875+
unexpectedBeforeIsolationKind,
876+
identifier: isolationKind,
877+
arena: self.arena
878+
)
879+
880+
return RawLabeledExprListSyntax(
881+
elements: [isolationKindElement],
882+
arena: self.arena
883+
)
884+
}
885+
}
886+
851887
extension Parser {
852888
mutating func parseBackDeployedAttributeArguments() -> RawBackDeployedAttributeArgumentsSyntax {
853889
let (unexpectedBeforeLabel, label) = self.expect(.keyword(.before))

Sources/SwiftParser/Lookahead.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ extension Parser.Lookahead {
163163
// Ok, it is a valid attribute, eat it, and then process it.
164164
self.eat(handle)
165165
switch attr {
166-
case .convention:
166+
case .convention, .isolated:
167167
self.skipSingle()
168168
default:
169169
break

Sources/SwiftParser/TokenPrecedence.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ enum TokenPrecedence: Comparable {
252252
.Sendable,
253253
.retroactive,
254254
.unchecked:
255+
// Note that .isolated is preferred as a decl keyword
255256
self = .exprKeyword
256257

257258
case // `DeclarationAttributeWithSpecialSyntax`

Sources/SwiftParser/TokenSpecSet.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@ enum TypeAttribute: TokenSpecSet {
643643
case retroactive
644644
case Sendable
645645
case unchecked
646+
case isolated
646647

647648
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
648649
switch PrepareForKeywordMatch(lexeme) {
@@ -660,6 +661,7 @@ enum TypeAttribute: TokenSpecSet {
660661
case TokenSpec(.Sendable): self = .Sendable
661662
case TokenSpec(.retroactive): self = .retroactive
662663
case TokenSpec(.unchecked): self = .unchecked
664+
case TokenSpec(.isolated): self = .isolated
663665
default: return nil
664666
}
665667
}
@@ -680,6 +682,7 @@ enum TypeAttribute: TokenSpecSet {
680682
case .retroactive: return .keyword(.retroactive)
681683
case .Sendable: return .keyword(.Sendable)
682684
case .unchecked: return .keyword(.unchecked)
685+
case .isolated: return .keyword(.isolated)
683686
}
684687
}
685688
}

Sources/SwiftParser/Types.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,15 @@ extension Parser {
179179
}
180180
}
181181

182+
/// Parse the subset of types that we allow in attribute names.
183+
mutating func parseAttributeName() -> RawTypeSyntax {
184+
return parseSimpleType(forAttributeName: true)
185+
}
186+
182187
/// Parse a "simple" type
183188
mutating func parseSimpleType(
184-
stopAtFirstPeriod: Bool = false
189+
stopAtFirstPeriod: Bool = false,
190+
forAttributeName: Bool = false
185191
) -> RawTypeSyntax {
186192
enum TypeBaseStart: TokenSpecSet {
187193
case `Self`
@@ -303,6 +309,11 @@ extension Parser {
303309
continue
304310
}
305311

312+
// Do not allow ? or ! suffixes when parsing attribute names.
313+
if forAttributeName {
314+
break
315+
}
316+
306317
if self.at(TokenSpec(.postfixQuestionMark, allowAtStartOfLine: false)) {
307318
base = RawTypeSyntax(self.parseOptionalType(base))
308319
continue
@@ -937,6 +948,10 @@ extension Parser {
937948
return parseAttribute(argumentMode: .required) { parser in
938949
return .opaqueReturnTypeOfAttributeArguments(parser.parseOpaqueReturnTypeOfAttributeArguments())
939950
}
951+
case .isolated:
952+
return parseAttribute(argumentMode: .required) { parser in
953+
return .argumentList(parser.parseIsolatedAttributeArguments())
954+
}
940955
case nil: // Custom attribute
941956
return parseAttribute(argumentMode: .customAttribute) { parser in
942957
let arguments = parser.parseArgumentListElements(pattern: .none)

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,4 +907,44 @@ final class AttributeTests: ParserTestCase {
907907
)
908908
)
909909
}
910+
911+
func testIsolatedTypeAttribute() {
912+
assertParse(
913+
"""
914+
var fn: @isolated(any) () -> ()
915+
"""
916+
)
917+
918+
// We don't validate the kind in the parser
919+
assertParse(
920+
"""
921+
var fn: @isolated(sdfhsdfi) () -> ()
922+
"""
923+
)
924+
925+
// Check that this combines correctly with other attributs.
926+
// This is not a valid combination, but we don't validate that here.
927+
assertParse(
928+
"""
929+
var fn: @isolated(any) @convention(swift) () -> ()
930+
"""
931+
)
932+
assertParse(
933+
"""
934+
var fn: @convention(swift) @isolated(any) () -> ()
935+
"""
936+
)
937+
938+
// Test that lookahead correctly skips the argument clause.
939+
assertParse(
940+
"""
941+
var array = [@isolated(any) @convention(swift) () -> ()]()
942+
"""
943+
)
944+
assertParse(
945+
"""
946+
var array = [@convention(swift) @isolated(any) () -> ()]()
947+
"""
948+
)
949+
}
910950
}

0 commit comments

Comments
 (0)