@@ -196,28 +196,35 @@ extension Parser {
196196 atSign = atSign. tokenView. withTokenDiagnostic ( tokenDiagnostic: diagnostic, arena: self . arena)
197197 }
198198 let attributeName = self . parseAttributeName ( )
199+ let attributeNameHasTrailingSpace = attributeName. raw. trailingTriviaByteLength > 0
200+
199201 let shouldParseArgument : Bool
200202 switch argumentMode {
201203 case . required:
202204 shouldParseArgument = true
203205 case . customAttribute:
204- shouldParseArgument = self . withLookahead { $0. atAttributeOrSpecifierArgument ( ) }
206+ shouldParseArgument = self . withLookahead {
207+ $0. atAttributeOrSpecifierArgument ( lastTokenHadSpace: attributeNameHasTrailingSpace, forCustomAttribute: true )
208+ }
205209 case . optional:
206- shouldParseArgument = self . at ( TokenSpec ( . leftParen, allowAtStartOfLine: false ) )
210+ shouldParseArgument = self . withLookahead {
211+ $0. atAttributeOrSpecifierArgument ( lastTokenHadSpace: attributeNameHasTrailingSpace, forCustomAttribute: false )
212+ }
207213 case . noArgument:
208214 shouldParseArgument = false
209215 }
210216 if shouldParseArgument {
211217 var ( unexpectedBeforeLeftParen, leftParen) = self . expect ( TokenSpec ( . leftParen, allowAtStartOfLine: false ) )
212- if unexpectedBeforeLeftParen == nil
213- && ( attributeName . raw . trailingTriviaByteLength > 0 || leftParen . leadingTriviaByteLength > 0 )
214- {
218+
219+ // Diagnose spaces between the name and the '('.
220+ if unexpectedBeforeLeftParen == nil && ( attributeNameHasTrailingSpace || leftParen . leadingTriviaByteLength > 0 ) {
215221 let diagnostic = TokenDiagnostic (
216222 self . swiftVersion < . v6 ? . extraneousLeadingWhitespaceWarning : . extraneousLeadingWhitespaceError,
217223 byteOffset: 0
218224 )
219225 leftParen = leftParen. tokenView. withTokenDiagnostic ( tokenDiagnostic: diagnostic, arena: self . arena)
220226 }
227+
221228 let unexpectedBeforeArguments : RawUnexpectedNodesSyntax ?
222229 let argument : RawAttributeSyntax . Arguments
223230 if let parseMissingArguments, leftParen. presence == . missing {
@@ -1074,44 +1081,70 @@ extension Parser {
10741081// MARK: Lookahead
10751082
10761083extension Parser . Lookahead {
1077- mutating func atAttributeOrSpecifierArgument( ) -> Bool {
1084+ mutating func atAttributeOrSpecifierArgument(
1085+ lastTokenHadSpace: Bool ,
1086+ forCustomAttribute: Bool = false
1087+ ) -> Bool {
10781088 if !self . at ( TokenSpec ( . leftParen, allowAtStartOfLine: false ) ) {
10791089 return false
10801090 }
10811091
1082- var lookahead = self . lookahead ( )
1083- lookahead. skipSingle ( )
1084-
1085- // If we have any keyword, identifier, or token that follows a function
1086- // type's parameter list, this is a parameter list and not an attribute.
1087- // Alternatively, we might have a token that illustrates we're not going to
1088- // get anything following the attribute, which means the parentheses describe
1089- // what follows the attribute.
1090- switch lookahead. currentToken {
1091- case TokenSpec ( . arrow) ,
1092- TokenSpec ( . throw) ,
1093- TokenSpec ( . throws) ,
1094- TokenSpec ( . rethrows) ,
1095- TokenSpec ( . rightParen) ,
1096- TokenSpec ( . rightBrace) ,
1097- TokenSpec ( . rightSquare) ,
1098- TokenSpec ( . rightAngle) :
1099- return false
1100- case _ where lookahead. at ( . keyword( . async) ) :
1101- return false
1102- case _ where lookahead. at ( . keyword( . reasync) ) :
1103- return false
1104- default :
1105- return true
1092+ if self . swiftVersion >= . v6 {
1093+ if !lastTokenHadSpace && currentToken. leadingTriviaByteLength == 0 {
1094+ return true
1095+ }
1096+
1097+ return withLookahead ( {
1098+ $0. skipSingle ( )
1099+ return $0. at ( . atSign) || $0. atStartOfDeclaration ( )
1100+ } )
1101+ } else {
1102+ if !forCustomAttribute {
1103+ return true
1104+ }
1105+ var lookahead = self . lookahead ( )
1106+ lookahead. skipSingle ( )
1107+
1108+ // If we have any keyword, identifier, or token that follows a function
1109+ // type's parameter list, this is a parameter list and not an attribute.
1110+ // Alternatively, we might have a token that illustrates we're not going to
1111+ // get anything following the attribute, which means the parentheses describe
1112+ // what follows the attribute.
1113+ switch lookahead. currentToken {
1114+ case TokenSpec ( . arrow) ,
1115+ TokenSpec ( . throw) ,
1116+ TokenSpec ( . throws) ,
1117+ TokenSpec ( . rethrows) ,
1118+ TokenSpec ( . rightParen) ,
1119+ TokenSpec ( . rightBrace) ,
1120+ TokenSpec ( . rightSquare) ,
1121+ TokenSpec ( . rightAngle) :
1122+ return false
1123+ case _ where lookahead. at ( . keyword( . async) ) :
1124+ return false
1125+ case _ where lookahead. at ( . keyword( . reasync) ) :
1126+ return false
1127+ default :
1128+ return true
1129+ }
11061130 }
11071131 }
11081132
11091133 mutating func canParseCustomAttribute( ) -> Bool {
1110- guard self . canParseType ( ) else {
1134+ guard
1135+ let numTypeTokens = self . withLookahead ( { $0. canParseSimpleType ( ) ? $0. tokensConsumed : nil } ) ,
1136+ numTypeTokens >= 1
1137+ else {
11111138 return false
11121139 }
1140+ // Check if the last token had trailing white spaces.
1141+ for _ in 0 ..< numTypeTokens - 1 {
1142+ self . consumeAnyToken ( )
1143+ }
1144+ let hasSpace = self . currentToken. trailingTriviaByteLength > 0
1145+ self . consumeAnyToken ( )
11131146
1114- if self . withLookahead ( { $0 . atAttributeOrSpecifierArgument ( ) } ) {
1147+ if self . atAttributeOrSpecifierArgument ( lastTokenHadSpace : hasSpace , forCustomAttribute : true ) {
11151148 self . skipSingle ( )
11161149 }
11171150
0 commit comments