Skip to content

Commit 60f0e62

Browse files
committed
Diagnose misplaced type specifiers in function types
1 parent 2ac18f1 commit 60f0e62

File tree

7 files changed

+58
-53
lines changed

7 files changed

+58
-53
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,7 +1313,7 @@ extension Parser {
13131313
switch self.at(anyIn: ParameterModifier.self) {
13141314
case (._const, let handle)?:
13151315
elements.append(RawDeclModifierSyntax(name: self.eat(handle), detail: nil, arena: self.arena))
1316-
case (.isolated, let handle)? where !self.lookahead().startsParameterName(subject.isClosure):
1316+
case (.isolated, let handle)? where self.withLookahead({ !$0.startsParameterName(isClosure: subject.isClosure, allowMisplacedSpecifierRecovery: false) }):
13171317
elements.append(RawDeclModifierSyntax(name: self.eat(handle), detail: nil, arena: self.arena))
13181318
default:
13191319
break MODIFIER_LOOP
@@ -1362,7 +1362,7 @@ extension Parser {
13621362
let colon: RawTokenSyntax?
13631363
let shouldParseType: Bool
13641364

1365-
if self.lookahead().startsParameterName(subject.isClosure) {
1365+
if self.withLookahead({ $0.startsParameterName(isClosure: subject.isClosure, allowMisplacedSpecifierRecovery: false) }) {
13661366
if self.currentToken.canBeArgumentLabel {
13671367
firstName = self.parseArgumentLabel()
13681368
} else {

Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
466466
unexpectedTokenCondition: { TypeSpecifier(token: $0) != nil },
467467
correctTokens: [node.type?.as(AttributedTypeSyntax.self)?.specifier],
468468
message: { SpecifierOnParameterName(misplacedSpecifiers: $0) },
469-
moveFixIt: { MoveTokensAfterTypeFixIt(movedTokens: $0) },
469+
moveFixIt: { MoveTokensInFrontOfTypeFixIt(movedTokens: $0) },
470470
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
471471
)
472472
return .visitChildren
@@ -550,6 +550,21 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
550550
return .visitChildren
551551
}
552552

553+
public override func visit(_ node: TupleTypeElementSyntax) -> SyntaxVisitorContinueKind {
554+
if shouldSkip(node) {
555+
return .skipChildren
556+
}
557+
exchangeTokens(
558+
unexpected: node.unexpectedBetweenInOutAndName,
559+
unexpectedTokenCondition: { TypeSpecifier(token: $0) != nil },
560+
correctTokens: [node.type.as(AttributedTypeSyntax.self)?.specifier],
561+
message: { SpecifierOnParameterName(misplacedSpecifiers: $0) },
562+
moveFixIt: { MoveTokensInFrontOfTypeFixIt(movedTokens: $0) },
563+
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
564+
)
565+
return .visitChildren
566+
}
567+
553568
public override func visit(_ node: TypeInitializerClauseSyntax) -> SyntaxVisitorContinueKind {
554569
if shouldSkip(node) {
555570
return .skipChildren

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,24 +226,24 @@ public struct MoveTokensAfterFixIt: ParserFixIt {
226226
}
227227
}
228228

229-
public struct MoveTokensAfterTypeFixIt: ParserFixIt {
229+
public struct MoveTokensInFrontOfFixIt: ParserFixIt {
230230
/// The token that should be moved
231231
public let movedTokens: [TokenSyntax]
232232

233+
/// The token after which 'try' should be moved
234+
public let inFrontOf: RawTokenKind
235+
233236
public var message: String {
234-
"move \(nodesDescription(movedTokens, format: false)) after type"
237+
"move \(nodesDescription(movedTokens, format: false)) in front of '\(inFrontOf.nameForDiagnostics)'"
235238
}
236239
}
237240

238-
public struct MoveTokensInFrontOfFixIt: ParserFixIt {
241+
public struct MoveTokensInFrontOfTypeFixIt: ParserFixIt {
239242
/// The token that should be moved
240243
public let movedTokens: [TokenSyntax]
241244

242-
/// The token after which 'try' should be moved
243-
public let inFrontOf: RawTokenKind
244-
245245
public var message: String {
246-
"move \(nodesDescription(movedTokens, format: false)) in front of '\(inFrontOf.nameForDiagnostics)'"
246+
"move \(nodesDescription(movedTokens, format: false)) in front of type"
247247
}
248248
}
249249

Sources/SwiftParser/Lookahead.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ extension Parser {
5151
public func lookahead() -> Lookahead {
5252
return Lookahead(cloning: self)
5353
}
54+
55+
public func withLookahead<T>(_ body: (_: inout Lookahead) -> T) -> T {
56+
var lookahead = lookahead()
57+
return body(&lookahead)
58+
}
5459
}
5560

5661
extension Parser.Lookahead {

Sources/SwiftParser/Patterns.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,13 @@ extension Parser.Lookahead {
316316

317317
/// Determine whether we are at the start of a parameter name when
318318
/// parsing a parameter.
319-
func startsParameterName(_ isClosure: Bool) -> Bool {
319+
/// If `allowMisplacedSpecifierRecovery` is `true`, then this will skip over any type
320+
/// specifiers before checking whether this lookahead starts a parameter name.
321+
mutating func startsParameterName(isClosure: Bool, allowMisplacedSpecifierRecovery: Bool) -> Bool {
322+
if allowMisplacedSpecifierRecovery {
323+
while self.consume(ifAnyIn: TypeSpecifier.self) != nil {}
324+
}
325+
320326
// To have a parameter name here, we need a name.
321327
guard self.currentToken.canBeArgumentLabel else {
322328
return false
@@ -341,12 +347,11 @@ extension Parser.Lookahead {
341347
// so look ahead one more token (two total) see if we have a ':' that would
342348
// indicate that this is an argument label.
343349
do {
344-
var backtrack = self.lookahead()
345-
if backtrack.at(.colon) {
350+
if self.at(.colon) {
346351
return true // isolated :
347352
}
348-
backtrack.consumeAnyToken()
349-
return backtrack.currentToken.canBeArgumentLabel && backtrack.peek().tokenKind == .colon
353+
self.consumeAnyToken()
354+
return self.currentToken.canBeArgumentLabel && self.peek().tokenKind == .colon
350355
}
351356
}
352357

Sources/SwiftParser/Types.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,11 @@ extension Parser {
402402
let second: RawTokenSyntax?
403403
let unexpectedBeforeColon: RawUnexpectedNodesSyntax?
404404
let colon: RawTokenSyntax?
405-
if self.lookahead().startsParameterName(false) {
405+
var misplacedSpecifiers: [RawTokenSyntax] = []
406+
if self.withLookahead({ $0.startsParameterName(isClosure: false, allowMisplacedSpecifierRecovery: true) }) {
407+
while let specifier = self.consume(ifAnyIn: TypeSpecifier.self) {
408+
misplacedSpecifiers.append(specifier)
409+
}
406410
first = self.parseArgumentLabel()
407411
if let parsedColon = self.consume(if: .colon) {
408412
second = nil
@@ -423,12 +427,13 @@ extension Parser {
423427
colon = nil
424428
}
425429
// Parse the type annotation.
426-
let type = self.parseType()
430+
let type = self.parseType(misplacedSpecifiers: misplacedSpecifiers)
427431
let ellipsis = self.currentToken.isEllipsis ? self.consumeAnyToken() : nil
428432
let trailingComma = self.consume(if: .comma)
429433
keepGoing = trailingComma != nil
430434
elements.append(RawTupleTypeElementSyntax(
431435
inOut: nil,
436+
RawUnexpectedNodesSyntax(misplacedSpecifiers, arena: self.arena),
432437
name: first,
433438
secondName: second,
434439
unexpectedBeforeColon,
@@ -593,7 +598,7 @@ extension Parser.Lookahead {
593598

594599
// If the tuple element starts with "ident :", then it is followed
595600
// by a type annotation.
596-
if self.startsParameterName(/*isClosure=*/false) {
601+
if self.startsParameterName(isClosure: false, allowMisplacedSpecifierRecovery: false) {
597602
self.consumeAnyToken()
598603
if self.currentToken.canBeArgumentLabel {
599604
self.consumeAnyToken()

Tests/SwiftParserTest/translated/InvalidTests.swift

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ final class InvalidTests: XCTestCase {
1111
""",
1212
diagnostics: [
1313
// TODO: Old parser expected warning on line 2: 'var' in this position is interpreted as an argument label, Fix-It replacements: 18 - 21 = '`var`'
14-
DiagnosticSpec(message: "'inout' before a parameter name is not allowed", fixIts: ["move 'inout' after type"]),
14+
DiagnosticSpec(message: "'inout' before a parameter name is not allowed", fixIts: ["move 'inout' in front of type"]),
1515
], fixedSource: "func test1(var x : inout Int) {}"
1616
)
1717
}
@@ -31,12 +31,11 @@ final class InvalidTests: XCTestCase {
3131
func testInvalid1c() {
3232
AssertParse(
3333
"""
34-
func test3(f : (inout _ 1️⃣x : Int) -> Void) {}
34+
func test3(f : (1️⃣inout _ x : Int) -> Void) {}
3535
""",
3636
diagnostics: [
37-
// TODO: Old parser expected error on line 1: 'inout' before a parameter name is not allowed
38-
DiagnosticSpec(message: "unexpected code 'x : Int' in function type"),
39-
]
37+
DiagnosticSpec(message: "'inout' before a parameter name is not allowed", fixIts: ["move 'inout' in front of type"]),
38+
], fixedSource: "func test3(f : (_ x : inout Int) -> Void) {}"
4039
)
4140
}
4241

@@ -97,7 +96,6 @@ final class InvalidTests: XCTestCase {
9796
""",
9897
diagnostics: [
9998
DiagnosticSpec(message: "expected value and ')' to end function call"),
100-
// TODO: Old parser expected error on line 3: expected expression in list of expressions
10199
]
102100
)
103101
}
@@ -120,7 +118,6 @@ final class InvalidTests: XCTestCase {
120118
}
121119
""",
122120
diagnostics: [
123-
// TODO: Old parser expected error on line 3: expected ',' separator, Fix-It replacements: 32 - 32 = ','
124121
DiagnosticSpec(message: "expected ')' to end function call"),
125122
]
126123
)
@@ -130,10 +127,7 @@ final class InvalidTests: XCTestCase {
130127
AssertParse(
131128
"""
132129
super.init()
133-
""",
134-
diagnostics: [
135-
// TODO: Old parser expected error on line 1: 'super' cannot be used outside of class members
136-
]
130+
"""
137131
)
138132
}
139133

@@ -186,22 +180,19 @@ final class InvalidTests: XCTestCase {
186180
_ = " >> \( abc 1️⃣} ) << "2️⃣
187181
"""##,
188182
diagnostics: [
189-
// TODO: Old parser expected error on line 4: expected ',' separator, Fix-It replacements: 18 - 18 = ','
190-
// TODO: Old parser expected error on line 4: expected expression in list of expressions
191183
DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in string literal"),
192184
DiagnosticSpec(locationMarker: "2️⃣", message: "expected '}' to end function"),
193185
]
194186
)
195187
}
196188

197189
func testInvalid11() {
190+
// rdar://problem/18507467
198191
AssertParse(
199192
"""
200-
// rdar://problem/18507467
201193
func d(_ b: 1️⃣String 2️⃣-> 3️⃣<T>() -> T4️⃣) {}
202194
""",
203195
diagnostics: [
204-
// TODO: Old parser expected error on line 2: expected type for function result
205196
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '(' to start function type"),
206197
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' in function type"),
207198
DiagnosticSpec(locationMarker: "3️⃣", message: "expected type in function type"),
@@ -218,11 +209,7 @@ final class InvalidTests: XCTestCase {
218209
protocol Animal<Food> {
219210
func feed(_ food: Food)
220211
}
221-
""",
222-
diagnostics: [
223-
// TODO: Old parser expected error on line 2: an associated type named 'Food' must be declared in the protocol 'Animal' or a protocol it inherits
224-
// TODO: Old parser expected error on line 3: cannot find type 'Food' in scope
225-
]
212+
"""
226213
)
227214
}
228215

@@ -240,7 +227,6 @@ final class InvalidTests: XCTestCase {
240227
}
241228
""",
242229
diagnostics: [
243-
// TODO: Old parser expected error on line 6: expected ':' following argument label and parameter name
244230
DiagnosticSpec(message: "expected ':' and type in function parameter"),
245231
]
246232
)
@@ -256,7 +242,6 @@ final class InvalidTests: XCTestCase {
256242
}
257243
""",
258244
diagnostics: [
259-
// TODO: Old parser expected error on line 4: unexpected ',' separator
260245
DiagnosticSpec(message: "expected value in function call"),
261246
]
262247
)
@@ -275,10 +260,7 @@ final class InvalidTests: XCTestCase {
275260
AssertParse(
276261
"""
277262
func f1_43591(a : inout inout Int) {}
278-
""",
279-
diagnostics: [
280-
// TODO: Old parser expected error on line 1: parameter must not have multiple '__owned', 'inout', or '__shared' specifiers, Fix-It replacements: 19 - 25 = ''
281-
]
263+
"""
282264
)
283265
}
284266

@@ -289,8 +271,7 @@ final class InvalidTests: XCTestCase {
289271
func f2_43591(1️⃣inout inout b: Int) {}
290272
""",
291273
diagnostics: [
292-
// TODO: Old parser expected error on line 2: parameter must not have multiple '__owned', 'inout', or '__shared' specifiers, Fix-It replacements: 21 - 27 = ''
293-
DiagnosticSpec(message: "'inout inout' before a parameter name is not allowed", fixIts: ["move 'inout inout' after type"]),
274+
DiagnosticSpec(message: "'inout inout' before a parameter name is not allowed", fixIts: ["move 'inout inout' in front of type"]),
294275
], fixedSource: "func f2_43591(b: inout Int) {}"
295276
)
296277
}
@@ -302,9 +283,6 @@ final class InvalidTests: XCTestCase {
302283
""",
303284
diagnostics: [
304285
// TODO: Old parser expected warning on line 3: 'let' in this position is interpreted as an argument label, Fix-It replacements: 15 - 18 = '`let`'
305-
// TODO: Old parser expected error on line 3: expected ',' separator, Fix-It replacements: 22 - 22 = ','
306-
// TODO: Old parser expected error on line 3: expected ':' following argument label and parameter name
307-
// TODO: Old parser expected warning on line 3: extraneous duplicate parameter name; 'let' already has an argument label, Fix-It replacements: 15 - 19 = ''
308286
DiagnosticSpec(message: "unexpected code 'a' in function parameter"),
309287
]
310288
)
@@ -316,7 +294,6 @@ final class InvalidTests: XCTestCase {
316294
func f4_43591(1️⃣inout x: inout String) {}
317295
""",
318296
diagnostics: [
319-
// TODO: Old parser expected error on line 4: parameter must not have multiple '__owned', 'inout', or '__shared' specifiers, Fix-It replacements: 15 - 20 = ''
320297
DiagnosticSpec(message: "'inout' before a parameter name is not allowed"),
321298
]
322299
)
@@ -391,11 +368,9 @@ final class InvalidTests: XCTestCase {
391368
func testInvalid22() {
392369
AssertParse(
393370
"""
394-
let 1️⃣for 2️⃣= 2
371+
let 1️⃣for = 2
395372
""",
396373
diagnostics: [
397-
// TODO: Old parser expected error on line 1: keyword 'for' cannot be used as an identifier here
398-
// TODO: Old parser expected note on line 1: if this name is unavoidable, use backticks to escape it, Fix-It replacements: 5 - 8 = '`for`'
399374
DiagnosticSpec(message: "keyword 'for' cannot be used as an identifier here"),
400375
]
401376
)

0 commit comments

Comments
 (0)