Skip to content

Commit 713f08f

Browse files
authored
Merge pull request #2466 from ahoppen/ahoppen/disallow-attribute-space
Disallow whitespace between `@`, attribute name and `(`
2 parents 528f129 + 156b74f commit 713f08f

17 files changed

+300
-118
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.parseAttributeName()
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 unexpectedBeforeLeftParen == nil && (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
@@ -1928,19 +1928,18 @@ extension Parser {
19281928
) -> RawMacroExpansionDeclSyntax {
19291929

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

Sources/SwiftParser/Expressions.swift

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,20 +1283,18 @@ extension Parser {
12831283
flavor: ExprFlavor
12841284
) -> RawMacroExpansionExprSyntax {
12851285
var (unexpectedBeforePound, pound) = self.expect(.pound)
1286-
if pound.trailingTriviaByteLength != 0 {
1286+
if pound.trailingTriviaByteLength > 0 || currentToken.leadingTriviaByteLength > 0 {
12871287
// If there are whitespaces after '#' diagnose.
1288-
unexpectedBeforePound = RawUnexpectedNodesSyntax(combining: unexpectedBeforePound, pound, arena: self.arena)
1289-
pound = self.missingToken(.pound)
1288+
let diagnostic = TokenDiagnostic(
1289+
.extraneousTrailingWhitespaceError,
1290+
byteOffset: pound.leadingTriviaByteLength + pound.tokenText.count
1291+
)
1292+
pound = pound.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
12901293
}
1291-
var unexpectedBeforeMacroName: RawUnexpectedNodesSyntax?
1292-
var macroName: RawTokenSyntax
1294+
let unexpectedBeforeMacroName: RawUnexpectedNodesSyntax?
1295+
let macroName: RawTokenSyntax
12931296
if !self.atStartOfLine {
12941297
(unexpectedBeforeMacroName, macroName) = self.expectIdentifier(allowKeywordsAsIdentifier: true)
1295-
if macroName.leadingTriviaByteLength != 0 {
1296-
// If there're whitespaces after '#' diagnose.
1297-
unexpectedBeforeMacroName = RawUnexpectedNodesSyntax(combining: unexpectedBeforeMacroName, macroName, arena: self.arena)
1298-
pound = self.missingToken(.identifier, text: macroName.tokenText)
1299-
}
13001298
} else {
13011299
unexpectedBeforeMacroName = nil
13021300
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: 23 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

@@ -129,6 +132,9 @@ public struct Parser {
129132
static let defaultMaximumNestingLevel = 256
130133
#endif
131134

135+
/// The Swift version as which source files should be parsed if no Swift version is explicitly specified in the parser.
136+
static let defaultSwiftVersion: SwiftVersion = .v6
137+
132138
var _emptyRawMultipleTrailingClosureElementListSyntax: RawMultipleTrailingClosureElementListSyntax?
133139

134140
/// Create an empty collection of the given type.
@@ -187,26 +193,28 @@ public struct Parser {
187193
/// arena is created automatically, and `input` copied into the
188194
/// arena. If non-`nil`, `input` must be within its registered
189195
/// source buffer or allocator.
196+
/// - swiftVersion: The version of Swift using which the file should be parsed.
197+
/// Defaults to the latest version.
190198
/// - experimentalFeatures: The experimental features enabled for the parser.
191199
private init(
192200
buffer input: UnsafeBufferPointer<UInt8>,
193201
maximumNestingLevel: Int?,
194202
parseTransition: IncrementalParseTransition?,
195203
arena: ParsingSyntaxArena?,
204+
swiftVersion: SwiftVersion?,
196205
experimentalFeatures: ExperimentalFeatures
197206
) {
198207
var input = input
199208
if let arena {
200209
self.arena = arena
201210
precondition(arena.contains(text: SyntaxText(baseAddress: input.baseAddress, count: input.count)))
202211
} else {
203-
self.arena = ParsingSyntaxArena(
204-
parseTriviaFunction: TriviaParser.parseTrivia(_:position:)
205-
)
212+
self.arena = ParsingSyntaxArena(parseTriviaFunction: TriviaParser.parseTrivia)
206213
input = self.arena.internSourceBuffer(input)
207214
}
208215

209216
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
217+
self.swiftVersion = swiftVersion ?? Self.defaultSwiftVersion
210218
self.experimentalFeatures = experimentalFeatures
211219
self.lookaheadTrackerOwner = LookaheadTrackerOwner()
212220

@@ -224,6 +232,7 @@ public struct Parser {
224232
string input: String,
225233
maximumNestingLevel: Int?,
226234
parseTransition: IncrementalParseTransition?,
235+
swiftVersion: SwiftVersion?,
227236
experimentalFeatures: ExperimentalFeatures
228237
) {
229238
var input = input
@@ -234,6 +243,7 @@ public struct Parser {
234243
maximumNestingLevel: maximumNestingLevel,
235244
parseTransition: parseTransition,
236245
arena: nil,
246+
swiftVersion: swiftVersion,
237247
experimentalFeatures: experimentalFeatures
238248
)
239249
}
@@ -243,13 +253,15 @@ public struct Parser {
243253
public init(
244254
_ input: String,
245255
maximumNestingLevel: Int? = nil,
246-
parseTransition: IncrementalParseTransition? = nil
256+
parseTransition: IncrementalParseTransition? = nil,
257+
swiftVersion: SwiftVersion? = nil
247258
) {
248259
// Chain to the private String initializer.
249260
self.init(
250261
string: input,
251262
maximumNestingLevel: maximumNestingLevel,
252263
parseTransition: parseTransition,
264+
swiftVersion: swiftVersion,
253265
experimentalFeatures: []
254266
)
255267
}
@@ -277,14 +289,16 @@ public struct Parser {
277289
_ input: UnsafeBufferPointer<UInt8>,
278290
maximumNestingLevel: Int? = nil,
279291
parseTransition: IncrementalParseTransition? = nil,
280-
arena: ParsingSyntaxArena? = nil
292+
arena: ParsingSyntaxArena? = nil,
293+
swiftVersion: SwiftVersion? = nil
281294
) {
282295
// Chain to the private buffer initializer.
283296
self.init(
284297
buffer: input,
285298
maximumNestingLevel: maximumNestingLevel,
286299
parseTransition: parseTransition,
287300
arena: arena,
301+
swiftVersion: swiftVersion,
288302
experimentalFeatures: []
289303
)
290304
}
@@ -296,13 +310,15 @@ public struct Parser {
296310
_ input: String,
297311
maximumNestingLevel: Int? = nil,
298312
parseTransition: IncrementalParseTransition? = nil,
313+
swiftVersion: SwiftVersion? = nil,
299314
experimentalFeatures: ExperimentalFeatures
300315
) {
301316
// Chain to the private String initializer.
302317
self.init(
303318
string: input,
304319
maximumNestingLevel: maximumNestingLevel,
305320
parseTransition: parseTransition,
321+
swiftVersion: swiftVersion,
306322
experimentalFeatures: experimentalFeatures
307323
)
308324
}
@@ -315,6 +331,7 @@ public struct Parser {
315331
maximumNestingLevel: Int? = nil,
316332
parseTransition: IncrementalParseTransition? = nil,
317333
arena: ParsingSyntaxArena? = nil,
334+
swiftVersion: SwiftVersion? = nil,
318335
experimentalFeatures: ExperimentalFeatures
319336
) {
320337
// Chain to the private buffer initializer.
@@ -323,6 +340,7 @@ public struct Parser {
323340
maximumNestingLevel: maximumNestingLevel,
324341
parseTransition: parseTransition,
325342
arena: arena,
343+
swiftVersion: swiftVersion,
326344
experimentalFeatures: experimentalFeatures
327345
)
328346
}

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)