Skip to content

Commit 8198297

Browse files
authored
Merge pull request #974 from ahoppen/ahoppen/initializer-as-type-annotation
Diagnose if initializer is used as type annotation
2 parents f8711c0 + 3333b63 commit 8198297

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
@@ -1735,7 +1735,7 @@ extension Parser {
17351735
var loopProgress = LoopProgressCondition()
17361736
repeat {
17371737

1738-
let (pattern, type) = self.parseTypedPattern()
1738+
var (pattern, typeAnnotation) = self.parseTypedPattern()
17391739

17401740
// Parse an initializer if present.
17411741
let initializer: RawInitializerClauseSyntax?
@@ -1754,6 +1754,33 @@ extension Parser {
17541754
value: value,
17551755
arena: self.arena
17561756
)
1757+
} else if self.at(.leftParen), !self.currentToken.isAtStartOfLine,
1758+
let typeAnnotationUnwrapped = typeAnnotation {
1759+
// If we have a '(' after the type in the annotation, the type annotation
1760+
// is probably a constructor call. Rewrite the nodes to remove the type
1761+
// annotation and form an initializer clause from it instead.
1762+
typeAnnotation = nil
1763+
let initExpr = parsePostfixExpressionSuffix(
1764+
RawExprSyntax(RawTypeExprSyntax(
1765+
type: typeAnnotationUnwrapped.type,
1766+
typeAnnotation?.unexpectedAfterType,
1767+
arena: self.arena
1768+
)),
1769+
.basic,
1770+
forDirective: false,
1771+
pattern: .none
1772+
)
1773+
initializer = RawInitializerClauseSyntax(
1774+
RawUnexpectedNodesSyntax(
1775+
(typeAnnotationUnwrapped.unexpectedBeforeColon?.elements ?? []) +
1776+
[RawSyntax(typeAnnotationUnwrapped.colon)] +
1777+
(typeAnnotationUnwrapped.unexpectedBetweenColonAndType?.elements ?? []),
1778+
arena: self.arena
1779+
),
1780+
equal: missingToken(.equal, text: nil),
1781+
value: initExpr,
1782+
arena: self.arena
1783+
)
17571784
} else {
17581785
initializer = nil
17591786
}
@@ -1768,7 +1795,7 @@ extension Parser {
17681795
keepGoing = self.consume(if: .comma)
17691796
elements.append(RawPatternBindingSyntax(
17701797
pattern: pattern,
1771-
typeAnnotation: type,
1798+
typeAnnotation: typeAnnotation,
17721799
initializer: initializer,
17731800
accessor: accessor,
17741801
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
@@ -2211,32 +2211,6 @@ extension Parser {
22112211
} while keepGoing != nil && loopProgress.evaluate(currentToken)
22122212
return result
22132213
}
2214-
2215-
/// Parse an argument list.
2216-
///
2217-
/// This is currently the same as parsing a tuple expression. In the future,
2218-
/// this will be a dedicated argument list type.
2219-
///
2220-
/// Grammar
2221-
/// =======
2222-
///
2223-
/// tuple-expression → '(' ')' | '(' tuple-element ',' tuple-element-list ')'
2224-
/// tuple-element-list → tuple-element | tuple-element ',' tuple-element-list
2225-
@_spi(RawSyntax)
2226-
public mutating func parseArgumentList(_ flavor: ExprFlavor, pattern: PatternContext) -> RawTupleExprSyntax {
2227-
let (unexpectedBeforeLParen, lparen) = self.expect(.leftParen)
2228-
let args = self.parseArgumentListElements(pattern: pattern)
2229-
let (unexpectedBeforeRightParen, rparen) = self.expect(.rightParen)
2230-
2231-
// FIXME: Introduce new SyntaxKind for ArgumentList (rdar://81786229)
2232-
return RawTupleExprSyntax(
2233-
unexpectedBeforeLParen,
2234-
leftParen: lparen,
2235-
elementList: RawTupleExprElementListSyntax(elements: args, arena: self.arena),
2236-
unexpectedBeforeRightParen,
2237-
rightParen: rparen,
2238-
arena: self.arena)
2239-
}
22402214
}
22412215

22422216
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)