Repository: swiftlang/swift-syntax (swift)

Issue:
Closure arguments parsed as attribute parameters
### Issue Kind

Parse of Valid Source Produced Invalid Syntax Tree

### Source Code

```swift
f { @Sendable (e: Int) in }
```


### Description

Parsing the code snippet, `swift-parser-cli print-tree` produces the tree

```
SourceFileSyntax
├─statements: CodeBlockItemListSyntax
│ ╰─[0]: CodeBlockItemSyntax
│   ╰─item: FunctionCallExprSyntax
│     ├─calledExpression: DeclReferenceExprSyntax
│     │ ╰─baseName: identifier("f")
│     ├─arguments: LabeledExprListSyntax
│     ├─trailingClosure: ClosureExprSyntax
│     │ ├─leftBrace: leftBrace
│     │ ├─signature: ClosureSignatureSyntax
│     │ │ ├─attributes: AttributeListSyntax
│     │ │ │ ╰─[0]: AttributeSyntax
│     │ │ │   ├─atSign: atSign
│     │ │ │   ├─attributeName: IdentifierTypeSyntax
│     │ │ │   │ ╰─name: identifier("Sendable")
│     │ │ │   ├─leftParen: leftParen
│     │ │ │   ├─arguments: LabeledExprListSyntax
│     │ │ │   │ ╰─[0]: LabeledExprSyntax
│     │ │ │   │   ├─label: identifier("e")
│     │ │ │   │   ├─colon: colon
│     │ │ │   │   ╰─expression: DeclReferenceExprSyntax
│     │ │ │   │     ╰─baseName: identifier("Int")
│     │ │ │   ╰─rightParen: rightParen
│     │ │ ╰─inKeyword: keyword(SwiftSyntax.Keyword.in)
│     │ ├─statements: CodeBlockItemListSyntax
│     │ ╰─rightBrace: rightBrace
│     ╰─additionalTrailingClosures: MultipleTrailingClosureElementListSyntax
╰─endOfFileToken: endOfFile
```

which shows that `(e: Int)` is interpreted as the parameter list of `@Sendable` while it is actually meant to be the argument list for the closure.

Is this an ambiguity only the compiler can resolve with full type information available?

Pull request:
Fixes #2367
rdar://118608697

Patch:
diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift
index 0429dfbc2b1..eb45787d610 100644
--- a/Sources/SwiftParser/Attributes.swift
+++ b/Sources/SwiftParser/Attributes.swift
@@ -62,6 +62,7 @@ extension Parser {
     case exclusivity
     case inline
     case objc
+    case Sendable
     case transpose
 
     init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
@@ -97,6 +98,7 @@ extension Parser {
       case TokenSpec(.exclusivity): self = .exclusivity
       case TokenSpec(.inline): self = .inline
       case TokenSpec(.objc): self = .objc
+      case TokenSpec(.Sendable): self = .Sendable
       case TokenSpec(.transpose): self = .transpose
       default:
         return nil
@@ -136,6 +138,7 @@ extension Parser {
       case .exclusivity: return .keyword(.exclusivity)
       case .inline: return .keyword(.inline)
       case .objc: return .keyword(.objc)
+      case .Sendable: return .keyword(.Sendable)
       case .transpose: return .keyword(.transpose)
       }
     }
@@ -163,6 +166,7 @@ extension Parser {
     case required
     case customAttribute
     case optional
+    case noArgument
   }
 
   mutating func parseAttribute(
@@ -179,6 +183,8 @@ extension Parser {
       shouldParseArgument = self.withLookahead { $0.atCustomAttributeArgument() } && self.at(TokenSpec(.leftParen, allowAtStartOfLine: false))
     case .optional:
       shouldParseArgument = self.at(.leftParen)
+    case .noArgument:
+      shouldParseArgument = false
     }
     if shouldParseArgument {
       let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
@@ -336,6 +342,10 @@ extension Parser {
           arena: self.arena
         )
       )
+    case .Sendable:
+      return parseAttribute(argumentMode: .noArgument) { parser in
+        preconditionFailure("Sendable has no argument")
+      }
     case nil:
       return parseAttribute(argumentMode: .customAttribute) { parser in
         let arguments = parser.parseArgumentListElements(pattern: .none)


Test patch:
diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift
index 853520a2be8..3edf0a6d2cf 100644
--- a/Tests/SwiftParserTest/ExpressionTests.swift
+++ b/Tests/SwiftParserTest/ExpressionTests.swift
@@ -2984,4 +2984,11 @@ final class StatementExpressionTests: ParserTestCase {
       fixedSource: #"[1: "hello", 2: "world"]"#
     )
   }
+
+  func testSendableAttributeInClosure() {
+    assertParse(
+      "f { @Sendable (e: Int) in }",
+      substructure: AttributeSyntax(attributeName: TypeSyntax("Sendable"))
+    )
+  }
 }