Skip to content

Commit 4d5d32a

Browse files
committed
Disallow whitespace between @, attribute name and (
Add a Swift version parameter to parser. When parsing in Swift 6 mode, disallow whitespace between `@`, the attribute’s name and `(`. In Swift 5 warn. Use this opportunity to refactor how we diagnose extraneous whitespace. rdar://121668395
1 parent 3ec17e0 commit 4d5d32a

18 files changed

+298
-119
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,14 @@ extension Parser {
176176
argumentMode: AttributeArgumentMode,
177177
parseArguments: (inout Parser) -> RawAttributeSyntax.Arguments
178178
) -> RawAttributeListSyntax.Element {
179-
let (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
179+
var (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
180+
if atSign.trailingTriviaByteLength > 0 || self.currentToken.leadingTriviaByteLength > 0 {
181+
let diagnostic = TokenDiagnostic(
182+
self.swiftVersion < .v6 ? .extraneousTrailingWhitespaceWarning : .extraneousTrailingWhitespaceError,
183+
byteOffset: atSign.leadingTriviaByteLength + atSign.tokenText.count
184+
)
185+
atSign = atSign.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
186+
}
180187
let attributeName = self.parseType()
181188
let shouldParseArgument: Bool
182189
switch argumentMode {
@@ -190,7 +197,14 @@ extension Parser {
190197
shouldParseArgument = false
191198
}
192199
if shouldParseArgument {
193-
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
200+
var (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
201+
if attributeName.raw.trailingTriviaByteLength > 0 || leftParen.leadingTriviaByteLength > 0 {
202+
let diagnostic = TokenDiagnostic(
203+
self.swiftVersion < .v6 ? .extraneousLeadingWhitespaceWarning : .extraneousLeadingWhitespaceError,
204+
byteOffset: 0
205+
)
206+
leftParen = leftParen.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
207+
}
194208
let argument = parseArguments(&self)
195209
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
196210
return .attribute(

Sources/SwiftParser/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ add_swift_syntax_library(SwiftParser
3030
Recovery.swift
3131
Specifiers.swift
3232
Statements.swift
33-
StringLiterals.swift
3433
StringLiteralRepresentedLiteralValue.swift
34+
StringLiterals.swift
35+
SwiftVersion.swift
3536
SyntaxUtils.swift
3637
TokenConsumer.swift
3738
TokenPrecedence.swift

Sources/SwiftParser/Declarations.swift

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1930,19 +1930,18 @@ extension Parser {
19301930
) -> RawMacroExpansionDeclSyntax {
19311931

19321932
var (unexpectedBeforePound, pound) = self.eat(handle)
1933-
if pound.trailingTriviaByteLength != 0 {
1934-
// `#` and the macro name must not be separated by a newline.
1935-
unexpectedBeforePound = RawUnexpectedNodesSyntax(combining: unexpectedBeforePound, pound, arena: self.arena)
1936-
pound = RawTokenSyntax(missing: .pound, text: "#", leadingTriviaPieces: pound.leadingTriviaPieces, arena: self.arena)
1933+
if pound.trailingTriviaByteLength > 0 || currentToken.leadingTriviaByteLength > 0 {
1934+
// If there are whitespaces after '#' diagnose.
1935+
let diagnostic = TokenDiagnostic(
1936+
.extraneousTrailingWhitespaceError,
1937+
byteOffset: pound.leadingTriviaByteLength + pound.tokenText.count
1938+
)
1939+
pound = pound.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
19371940
}
1938-
var unexpectedBeforeMacro: RawUnexpectedNodesSyntax?
1939-
var macro: RawTokenSyntax
1941+
let unexpectedBeforeMacro: RawUnexpectedNodesSyntax?
1942+
let macro: RawTokenSyntax
19401943
if !self.atStartOfLine {
19411944
(unexpectedBeforeMacro, macro) = self.expectIdentifier(allowKeywordsAsIdentifier: true)
1942-
if macro.leadingTriviaByteLength != 0 {
1943-
unexpectedBeforeMacro = RawUnexpectedNodesSyntax(combining: unexpectedBeforeMacro, macro, arena: self.arena)
1944-
pound = self.missingToken(.identifier, text: macro.tokenText)
1945-
}
19461945
} else {
19471946
unexpectedBeforeMacro = nil
19481947
macro = self.missingToken(.identifier)

Sources/SwiftParser/Expressions.swift

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,20 +1271,18 @@ extension Parser {
12711271
flavor: ExprFlavor
12721272
) -> RawMacroExpansionExprSyntax {
12731273
var (unexpectedBeforePound, pound) = self.expect(.pound)
1274-
if pound.trailingTriviaByteLength != 0 {
1274+
if pound.trailingTriviaByteLength > 0 || currentToken.leadingTriviaByteLength > 0 {
12751275
// If there are whitespaces after '#' diagnose.
1276-
unexpectedBeforePound = RawUnexpectedNodesSyntax(combining: unexpectedBeforePound, pound, arena: self.arena)
1277-
pound = self.missingToken(.pound)
1276+
let diagnostic = TokenDiagnostic(
1277+
.extraneousTrailingWhitespaceError,
1278+
byteOffset: pound.leadingTriviaByteLength + pound.tokenText.count
1279+
)
1280+
pound = pound.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
12781281
}
1279-
var unexpectedBeforeMacroName: RawUnexpectedNodesSyntax?
1280-
var macroName: RawTokenSyntax
1282+
let unexpectedBeforeMacroName: RawUnexpectedNodesSyntax?
1283+
let macroName: RawTokenSyntax
12811284
if !self.atStartOfLine {
12821285
(unexpectedBeforeMacroName, macroName) = self.expectIdentifier(allowKeywordsAsIdentifier: true)
1283-
if macroName.leadingTriviaByteLength != 0 {
1284-
// If there're whitespaces after '#' diagnose.
1285-
unexpectedBeforeMacroName = RawUnexpectedNodesSyntax(combining: unexpectedBeforeMacroName, macroName, arena: self.arena)
1286-
pound = self.missingToken(.identifier, text: macroName.tokenText)
1287-
}
12881286
} else {
12891287
unexpectedBeforeMacroName = nil
12901288
macroName = self.missingToken(.identifier)

Sources/SwiftParser/Lexer/Lexeme.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ extension Lexer {
102102
SyntaxText(baseAddress: start, count: byteLength)
103103
}
104104

105-
var textRange: Range<SyntaxText.Index> {
105+
@_spi(Testing)
106+
public var textRange: Range<SyntaxText.Index> {
106107
leadingTriviaByteLength..<leadingTriviaByteLength + textByteLength
107108
}
108109

Sources/SwiftParser/Lookahead.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,30 @@ extension Parser {
2727
/// i.e. how far it looked ahead.
2828
var tokensConsumed: Int = 0
2929

30+
/// The Swift version as which this source file should be parsed.
31+
let swiftVersion: SwiftVersion
32+
3033
/// The experimental features that have been enabled in the underlying
3134
/// parser.
3235
let experimentalFeatures: ExperimentalFeatures
3336

3437
private init(
3538
lexemes: Lexer.LexemeSequence,
3639
currentToken: Lexer.Lexeme,
40+
swiftVersion: SwiftVersion,
3741
experimentalFeatures: ExperimentalFeatures
3842
) {
3943
self.lexemes = lexemes
4044
self.currentToken = currentToken
45+
self.swiftVersion = swiftVersion
4146
self.experimentalFeatures = experimentalFeatures
4247
}
4348

4449
fileprivate init(cloning other: Parser) {
4550
self.init(
4651
lexemes: other.lexemes,
4752
currentToken: other.currentToken,
53+
swiftVersion: other.swiftVersion,
4854
experimentalFeatures: other.experimentalFeatures
4955
)
5056
}
@@ -55,6 +61,7 @@ extension Parser {
5561
return Lookahead(
5662
lexemes: self.lexemes,
5763
currentToken: self.currentToken,
64+
swiftVersion: self.swiftVersion,
5865
experimentalFeatures: self.experimentalFeatures
5966
)
6067
}

Sources/SwiftParser/ParseSourceFile.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,21 @@ extension Parser {
2626
@_spi(ExperimentalLanguageFeatures)
2727
public static func parse(
2828
source: UnsafeBufferPointer<UInt8>,
29+
swiftVersion: SwiftVersion? = nil,
2930
experimentalFeatures: ExperimentalFeatures
3031
) -> SourceFileSyntax {
31-
var parser = Parser(source, experimentalFeatures: experimentalFeatures)
32+
var parser = Parser(source, swiftVersion: swiftVersion, experimentalFeatures: experimentalFeatures)
3233
return SourceFileSyntax.parse(from: &parser)
3334
}
3435

3536
/// Parse the source code in the given buffer as Swift source file. See
3637
/// `Parser.init` for more details.
3738
public static func parse(
3839
source: UnsafeBufferPointer<UInt8>,
39-
maximumNestingLevel: Int? = nil
40+
maximumNestingLevel: Int? = nil,
41+
swiftVersion: SwiftVersion? = nil
4042
) -> SourceFileSyntax {
41-
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel)
43+
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel, swiftVersion: swiftVersion)
4244
return SourceFileSyntax.parse(from: &parser)
4345
}
4446

Sources/SwiftParser/Parser.swift

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ public struct Parser {
117117
/// Parser should own a ``LookaheadTracker`` so that we can share one `furthestOffset` in a parse.
118118
let lookaheadTrackerOwner: LookaheadTrackerOwner
119119

120+
/// The Swift version as which this source file should be parsed.
121+
let swiftVersion: SwiftVersion
122+
120123
/// The experimental features that have been enabled.
121124
let experimentalFeatures: ExperimentalFeatures
122125

@@ -187,26 +190,28 @@ public struct Parser {
187190
/// arena is created automatically, and `input` copied into the
188191
/// arena. If non-`nil`, `input` must be within its registered
189192
/// source buffer or allocator.
193+
/// - swiftVersion: The version of Swift using which the file should be parsed.
194+
/// Defaults to the latest version.
190195
/// - experimentalFeatures: The experimental features enabled for the parser.
191196
private init(
192197
buffer input: UnsafeBufferPointer<UInt8>,
193198
maximumNestingLevel: Int?,
194199
parseTransition: IncrementalParseTransition?,
195200
arena: ParsingSyntaxArena?,
201+
swiftVersion: SwiftVersion?,
196202
experimentalFeatures: ExperimentalFeatures
197203
) {
198204
var input = input
199205
if let arena {
200206
self.arena = arena
201207
precondition(arena.contains(text: SyntaxText(baseAddress: input.baseAddress, count: input.count)))
202208
} else {
203-
self.arena = ParsingSyntaxArena(
204-
parseTriviaFunction: TriviaParser.parseTrivia(_:position:)
205-
)
209+
self.arena = ParsingSyntaxArena(parseTriviaFunction: TriviaParser.parseTrivia)
206210
input = self.arena.internSourceBuffer(input)
207211
}
208212

209213
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
214+
self.swiftVersion = swiftVersion ?? .v6
210215
self.experimentalFeatures = experimentalFeatures
211216
self.lookaheadTrackerOwner = LookaheadTrackerOwner()
212217

@@ -224,6 +229,7 @@ public struct Parser {
224229
string input: String,
225230
maximumNestingLevel: Int?,
226231
parseTransition: IncrementalParseTransition?,
232+
swiftVersion: SwiftVersion?,
227233
experimentalFeatures: ExperimentalFeatures
228234
) {
229235
var input = input
@@ -234,6 +240,7 @@ public struct Parser {
234240
maximumNestingLevel: maximumNestingLevel,
235241
parseTransition: parseTransition,
236242
arena: nil,
243+
swiftVersion: swiftVersion,
237244
experimentalFeatures: experimentalFeatures
238245
)
239246
}
@@ -243,13 +250,15 @@ public struct Parser {
243250
public init(
244251
_ input: String,
245252
maximumNestingLevel: Int? = nil,
246-
parseTransition: IncrementalParseTransition? = nil
253+
parseTransition: IncrementalParseTransition? = nil,
254+
swiftVersion: SwiftVersion? = nil
247255
) {
248256
// Chain to the private String initializer.
249257
self.init(
250258
string: input,
251259
maximumNestingLevel: maximumNestingLevel,
252260
parseTransition: parseTransition,
261+
swiftVersion: swiftVersion,
253262
experimentalFeatures: []
254263
)
255264
}
@@ -277,14 +286,16 @@ public struct Parser {
277286
_ input: UnsafeBufferPointer<UInt8>,
278287
maximumNestingLevel: Int? = nil,
279288
parseTransition: IncrementalParseTransition? = nil,
280-
arena: ParsingSyntaxArena? = nil
289+
arena: ParsingSyntaxArena? = nil,
290+
swiftVersion: SwiftVersion? = nil
281291
) {
282292
// Chain to the private buffer initializer.
283293
self.init(
284294
buffer: input,
285295
maximumNestingLevel: maximumNestingLevel,
286296
parseTransition: parseTransition,
287297
arena: arena,
298+
swiftVersion: swiftVersion,
288299
experimentalFeatures: []
289300
)
290301
}
@@ -296,13 +307,15 @@ public struct Parser {
296307
_ input: String,
297308
maximumNestingLevel: Int? = nil,
298309
parseTransition: IncrementalParseTransition? = nil,
310+
swiftVersion: SwiftVersion? = nil,
299311
experimentalFeatures: ExperimentalFeatures
300312
) {
301313
// Chain to the private String initializer.
302314
self.init(
303315
string: input,
304316
maximumNestingLevel: maximumNestingLevel,
305317
parseTransition: parseTransition,
318+
swiftVersion: swiftVersion,
306319
experimentalFeatures: experimentalFeatures
307320
)
308321
}
@@ -315,6 +328,7 @@ public struct Parser {
315328
maximumNestingLevel: Int? = nil,
316329
parseTransition: IncrementalParseTransition? = nil,
317330
arena: ParsingSyntaxArena? = nil,
331+
swiftVersion: SwiftVersion? = nil,
318332
experimentalFeatures: ExperimentalFeatures
319333
) {
320334
// Chain to the private buffer initializer.
@@ -323,6 +337,7 @@ public struct Parser {
323337
maximumNestingLevel: maximumNestingLevel,
324338
parseTransition: parseTransition,
325339
arena: arena,
340+
swiftVersion: swiftVersion,
326341
experimentalFeatures: experimentalFeatures
327342
)
328343
}

Sources/SwiftParser/StringLiterals.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ fileprivate class StringLiteralExpressionIndentationChecker {
6464
// error is fixed
6565
return nil
6666
}
67-
return token.tokenView.withTokenDiagnostic(
67+
let tokenWithDiagnostic = token.tokenView.withTokenDiagnostic(
6868
tokenDiagnostic: TokenDiagnostic(.insufficientIndentationInMultilineStringLiteral, byteOffset: 0),
6969
arena: arena
7070
)
71+
return RawSyntax(tokenWithDiagnostic)
7172
}
7273

7374
private func visitLayoutNode(node: RawSyntax) -> RawSyntax? {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
extension Parser {
14+
/// A Swift language version.
15+
public enum SwiftVersion: Comparable {
16+
case v4
17+
case v5
18+
case v6
19+
}
20+
}

Sources/SwiftParser/TokenConsumer.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ protocol TokenConsumer {
1818
/// The current token syntax being examined by the consumer
1919
var currentToken: Lexer.Lexeme { get }
2020

21+
var swiftVersion: Parser.SwiftVersion { get }
22+
2123
/// The experimental features that have been enabled.
2224
var experimentalFeatures: Parser.ExperimentalFeatures { get }
2325

0 commit comments

Comments
 (0)