Skip to content

Commit 3333b63

Browse files
committed
Diagnose if initializer is used as type annotation
1 parent be959d1 commit 3333b63

File tree

5 files changed

+182
-88
lines changed

5 files changed

+182
-88
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,7 +1713,7 @@ extension Parser {
17131713
var loopProgress = LoopProgressCondition()
17141714
repeat {
17151715

1716-
let (pattern, type) = self.parseTypedPattern()
1716+
var (pattern, typeAnnotation) = self.parseTypedPattern()
17171717

17181718
// Parse an initializer if present.
17191719
let initializer: RawInitializerClauseSyntax?
@@ -1732,6 +1732,33 @@ extension Parser {
17321732
value: value,
17331733
arena: self.arena
17341734
)
1735+
} else if self.at(.leftParen), !self.currentToken.isAtStartOfLine,
1736+
let typeAnnotationUnwrapped = typeAnnotation {
1737+
// If we have a '(' after the type in the annotation, the type annotation
1738+
// is probably a constructor call. Rewrite the nodes to remove the type
1739+
// annotation and form an initializer clause from it instead.
1740+
typeAnnotation = nil
1741+
let initExpr = parsePostfixExpressionSuffix(
1742+
RawExprSyntax(RawTypeExprSyntax(
1743+
type: typeAnnotationUnwrapped.type,
1744+
typeAnnotation?.unexpectedAfterType,
1745+
arena: self.arena
1746+
)),
1747+
.basic,
1748+
forDirective: false,
1749+
pattern: .none
1750+
)
1751+
initializer = RawInitializerClauseSyntax(
1752+
RawUnexpectedNodesSyntax(
1753+
(typeAnnotationUnwrapped.unexpectedBeforeColon?.elements ?? []) +
1754+
[RawSyntax(typeAnnotationUnwrapped.colon)] +
1755+
(typeAnnotationUnwrapped.unexpectedBetweenColonAndType?.elements ?? []),
1756+
arena: self.arena
1757+
),
1758+
equal: missingToken(.equal, text: nil),
1759+
value: initExpr,
1760+
arena: self.arena
1761+
)
17351762
} else {
17361763
initializer = nil
17371764
}
@@ -1746,7 +1773,7 @@ extension Parser {
17461773
keepGoing = self.consume(if: .comma)
17471774
elements.append(RawPatternBindingSyntax(
17481775
pattern: pattern,
1749-
typeAnnotation: type,
1776+
typeAnnotation: typeAnnotation,
17501777
initializer: initializer,
17511778
accessor: accessor,
17521779
trailingComma: keepGoing,

Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
529529
return .visitChildren
530530
}
531531

532+
public override func visit(_ node: InitializerClauseSyntax) -> SyntaxVisitorContinueKind {
533+
if shouldSkip(node) {
534+
return .skipChildren
535+
}
536+
if node.equal.presence == .missing {
537+
exchangeTokens(
538+
unexpected: node.unexpectedBeforeEqual,
539+
unexpectedTokenCondition: { $0.tokenKind == .colon },
540+
correctTokens: [node.equal],
541+
message: { _ in StaticParserError.initializerInPattern },
542+
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: node.equal) }
543+
)
544+
}
545+
return .visitChildren
546+
}
547+
532548
public override func visit(_ node: PrecedenceGroupAssignmentSyntax) -> SyntaxVisitorContinueKind {
533549
if shouldSkip(node) {
534550
return .skipChildren

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ extension DiagnosticMessage where Self == StaticParserError {
125125
public static var expectedExpressionAfterTry: Self {
126126
.init("expected expression after 'try'")
127127
}
128+
public static var initializerInPattern: Self {
129+
.init("unexpected initializer in pattern; did you mean to use '='?")
130+
}
128131
public static var invalidFlagAfterPrecedenceGroupAssignment: Self {
129132
.init("expected 'true' or 'false' after 'assignment'")
130133
}

Sources/SwiftParser/Expressions.swift

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,32 +2246,6 @@ extension Parser {
22462246
} while keepGoing != nil && loopProgress.evaluate(currentToken)
22472247
return result
22482248
}
2249-
2250-
/// Parse an argument list.
2251-
///
2252-
/// This is currently the same as parsing a tuple expression. In the future,
2253-
/// this will be a dedicated argument list type.
2254-
///
2255-
/// Grammar
2256-
/// =======
2257-
///
2258-
/// tuple-expression → '(' ')' | '(' tuple-element ',' tuple-element-list ')'
2259-
/// tuple-element-list → tuple-element | tuple-element ',' tuple-element-list
2260-
@_spi(RawSyntax)
2261-
public mutating func parseArgumentList(_ flavor: ExprFlavor, pattern: PatternContext) -> RawTupleExprSyntax {
2262-
let (unexpectedBeforeLParen, lparen) = self.expect(.leftParen)
2263-
let args = self.parseArgumentListElements(pattern: pattern)
2264-
let (unexpectedBeforeRightParen, rparen) = self.expect(.rightParen)
2265-
2266-
// FIXME: Introduce new SyntaxKind for ArgumentList (rdar://81786229)
2267-
return RawTupleExprSyntax(
2268-
unexpectedBeforeLParen,
2269-
leftParen: lparen,
2270-
elementList: RawTupleExprElementListSyntax(elements: args, arena: self.arena),
2271-
unexpectedBeforeRightParen,
2272-
rightParen: rparen,
2273-
arena: self.arena)
2274-
}
22752249
}
22762250

22772251
extension Parser {

Tests/SwiftParserTest/translated/DiagnoseInitializerAsTypedPatternTests.swift

Lines changed: 134 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,72 @@
22

33
import XCTest
44

5+
// https://github.com/apple/swift/issues/44070
56
final class DiagnoseInitializerAsTypedPatternTests: XCTestCase {
6-
func testDiagnoseInitializerAsTypedPattern1() {
7+
func testDiagnoseInitializerAsTypedPattern3a() {
78
AssertParse(
89
"""
9-
// https://github.com/apple/swift/issues/44070
10-
"""
10+
let a1️⃣:[X]()
11+
""",
12+
diagnostics: [
13+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
14+
], fixedSource: "let a=[X]()"
1115
)
1216
}
1317

14-
func testDiagnoseInitializerAsTypedPattern2() {
18+
func testDiagnoseInitializerAsTypedPattern3b() {
1519
AssertParse(
1620
"""
17-
class X {}
18-
func foo() {}
21+
let b1️⃣: [X]()
22+
""",
23+
diagnostics: [
24+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
25+
], fixedSource: "let b= [X]()"
26+
)
27+
}
28+
29+
func testDiagnoseInitializerAsTypedPattern3c() {
30+
AssertParse(
1931
"""
32+
let c 1️⃣:[X]()
33+
""",
34+
diagnostics: [
35+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
36+
], fixedSource: "let c =[X]()"
2037
)
2138
}
2239

23-
func testDiagnoseInitializerAsTypedPattern3() {
40+
func testDiagnoseInitializerAsTypedPattern3d() {
2441
AssertParse(
2542
"""
26-
let a:[X]1️⃣()
27-
let b: [X]2️⃣()
28-
let c :[X]3️⃣()
29-
let d : [X]4️⃣()
43+
let d 1️⃣: [X]()
3044
""",
3145
diagnostics: [
32-
// TODO: Old parser expected error on line 1: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' = '
33-
DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'"),
34-
// TODO: Old parser expected error on line 2: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' ='
35-
DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'"),
36-
// TODO: Old parser expected error on line 3: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 7 - 8 = '= '
37-
DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive statements on a line must be separated by ';'"),
38-
// TODO: Old parser expected error on line 4: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 7 - 8 = '='
39-
DiagnosticSpec(locationMarker: "4️⃣", message: "consecutive statements on a line must be separated by ';'"),
40-
]
46+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
47+
], fixedSource: "let d = [X]()"
4148
)
4249
}
4350

51+
4452
func testDiagnoseInitializerAsTypedPattern4() {
4553
AssertParse(
4654
"""
47-
let e: X1️⃣()2️⃣, ee: Int
55+
let e1️⃣: X(), ee: Int
4856
""",
4957
diagnostics: [
50-
// TODO: Old parser expected error on line 1: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' ='
51-
DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'"),
52-
DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code ', ee: Int' at top level"),
53-
]
58+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?"),
59+
], fixedSource: "let e= X(), ee: Int"
5460
)
5561
}
5662

5763
func testDiagnoseInitializerAsTypedPattern5() {
5864
AssertParse(
5965
"""
60-
let f:/*comment*/[X]1️⃣()
66+
let f1️⃣:/*comment*/[X]()
6167
""",
6268
diagnostics: [
63-
// TODO: Old parser expected error on line 1: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' = '
64-
DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'")
65-
]
69+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
70+
], fixedSource: "let f=/*comment*/[X]()"
6671
)
6772
}
6873

@@ -75,9 +80,9 @@ final class DiagnoseInitializerAsTypedPatternTests: XCTestCase {
7580
}
7681

7782
func testDiagnoseInitializerAsTypedPattern7() {
83+
// paren follows the type, but it's part of a separate (valid) expression
7884
AssertParse(
7985
"""
80-
// paren follows the type, but it's part of a separate (valid) expression
8186
let ff: X
8287
(_1, _2) = (_2, _1)
8388
let fff: X
@@ -86,52 +91,121 @@ final class DiagnoseInitializerAsTypedPatternTests: XCTestCase {
8691
)
8792
}
8893

89-
func testDiagnoseInitializerAsTypedPattern8() {
94+
func testDiagnoseInitializerAsTypedPattern8a() {
95+
AssertParse(
96+
"""
97+
let g1️⃣: X(x)
98+
""",
99+
diagnostics: [
100+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
101+
]
102+
)
103+
}
104+
105+
func testDiagnoseInitializerAsTypedPattern8b() {
106+
AssertParse(
107+
"""
108+
let h1️⃣: X(x, y)
109+
""",
110+
diagnostics: [
111+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
112+
]
113+
)
114+
}
115+
116+
func testDiagnoseInitializerAsTypedPattern8c() {
117+
AssertParse(
118+
"""
119+
let i1️⃣: X() { foo() }
120+
""",
121+
diagnostics: [
122+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
123+
]
124+
)
125+
}
126+
127+
func testDiagnoseInitializerAsTypedPattern8d() {
90128
AssertParse(
91129
"""
92-
let g: X1️⃣(x)
93-
let h: X2️⃣(x, y)
94-
let i: X3️⃣() { foo() }
95-
let j: X4️⃣(x) { foo() }
96-
let k: X5️⃣(x, y) { foo() }
130+
let j1️⃣: X(x) { foo() }
97131
""",
98132
diagnostics: [
99-
// TODO: Old parser expected error on line 1: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' ='
100-
DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'"),
101-
// TODO: Old parser expected error on line 2: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' ='
102-
DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'"),
103-
// TODO: Old parser expected error on line 3: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' ='
104-
DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive statements on a line must be separated by ';'"),
105-
// TODO: Old parser expected error on line 4: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' ='
106-
DiagnosticSpec(locationMarker: "4️⃣", message: "consecutive statements on a line must be separated by ';'"),
107-
// TODO: Old parser expected error on line 5: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 6 - 7 = ' ='
108-
DiagnosticSpec(locationMarker: "5️⃣", message: "consecutive statements on a line must be separated by ';'"),
133+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
109134
]
110135
)
111136
}
112137

113-
func testDiagnoseInitializerAsTypedPattern9() {
138+
func testDiagnoseInitializerAsTypedPattern8e() {
139+
AssertParse(
140+
"""
141+
let k1️⃣: X(x, y) { foo() }
142+
""",
143+
diagnostics: [
144+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
145+
]
146+
)
147+
}
148+
149+
func testDiagnoseInitializerAsTypedPattern9a() {
114150
AssertParse(
115151
"""
116152
func nonTopLevel() {
117-
let a:[X]1️⃣()
118-
let i: X2️⃣() { foo() }
119-
let j: X3️⃣(x) { foo() }
120-
let k: X4️⃣(x, y) { foo() }
121-
_ = (a, i, j, k)
153+
let a1️⃣:[X]()
154+
}
155+
""",
156+
diagnostics: [
157+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
158+
]
159+
)
160+
}
161+
162+
func testDiagnoseInitializerAsTypedPattern9b() {
163+
AssertParse(
164+
"""
165+
func nonTopLevel() {
166+
let i1️⃣: X() { foo() }
122167
}
123168
""",
124169
diagnostics: [
125-
// TODO: Old parser expected error on line 2: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 8 - 9 = ' = '
126-
DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'"),
127-
// TODO: Old parser expected error on line 3: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 8 - 9 = ' ='
128-
DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'"),
129-
// TODO: Old parser expected error on line 4: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 8 - 9 = ' ='
130-
DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive statements on a line must be separated by ';'"),
131-
// TODO: Old parser expected error on line 5: unexpected initializer in pattern; did you mean to use '='?, Fix-It replacements: 8 - 9 = ' ='
132-
DiagnosticSpec(locationMarker: "4️⃣", message: "consecutive statements on a line must be separated by ';'"),
170+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
133171
]
134172
)
135173
}
136174

175+
func testDiagnoseInitializerAsTypedPattern9c() {
176+
AssertParse(
177+
"""
178+
func nonTopLevel() {
179+
let j1️⃣: X(x) { foo() }
180+
}
181+
""",
182+
diagnostics: [
183+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
184+
]
185+
)
186+
}
187+
188+
func testDiagnoseInitializerAsTypedPattern9d() {
189+
AssertParse(
190+
"""
191+
func nonTopLevel() {
192+
let k1️⃣: X(x, y) { foo() }
193+
}
194+
""",
195+
diagnostics: [
196+
DiagnosticSpec(message: "unexpected initializer in pattern; did you mean to use '='?", fixIts: ["replace ':' by '='"]),
197+
]
198+
)
199+
}
200+
201+
func testDiagnoseInitializerAsTypedPattern9e() {
202+
AssertParse(
203+
"""
204+
func nonTopLevel() {
205+
_ = (a, i, j, k)
206+
}
207+
"""
208+
)
209+
}
210+
137211
}

0 commit comments

Comments
 (0)