Skip to content

Commit 61cc73e

Browse files
authored
Merge pull request #1450 from kimdv/kimdv/add-diagnostic-for-wrong-inheritance
Add diagnostic for wrong inheritance
2 parents a648679 + b2a371c commit 61cc73e

File tree

4 files changed

+144
-33
lines changed

4 files changed

+144
-33
lines changed

Sources/SwiftParser/Nominals.swift

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ extension Parser {
240240
}
241241

242242
let inheritance: RawTypeInheritanceClauseSyntax?
243-
if self.at(.colon) {
243+
if self.at(.colon) || self.isAtPythonStyleInheritanceClause() {
244244
inheritance = self.parseInheritance()
245245
} else {
246246
inheritance = nil
@@ -273,7 +273,20 @@ extension Parser {
273273
/// Parse an inheritance clause.
274274
@_spi(RawSyntax)
275275
public mutating func parseInheritance() -> RawTypeInheritanceClauseSyntax {
276-
let (unexpectedBeforeColon, colon) = self.expect(.colon)
276+
let unexpectedBeforeColon: RawUnexpectedNodesSyntax?
277+
let colon: RawTokenSyntax
278+
279+
let isPythonStyleInheritanceClause: Bool
280+
// Parse python style inheritance clause and replace parentheses with a colon
281+
if let leftParen = self.consume(if: .leftParen) {
282+
unexpectedBeforeColon = RawUnexpectedNodesSyntax([leftParen], arena: self.arena)
283+
colon = missingToken(.colon)
284+
isPythonStyleInheritanceClause = true
285+
} else {
286+
(unexpectedBeforeColon, colon) = self.expect(.colon)
287+
isPythonStyleInheritanceClause = false
288+
}
289+
277290
var elements = [RawInheritedTypeSyntax]()
278291
do {
279292
var keepGoing: RawTokenSyntax? = nil
@@ -304,10 +317,21 @@ extension Parser {
304317
)
305318
} while keepGoing != nil && loopProgress.evaluate(currentToken)
306319
}
320+
321+
let unexpectedAfterInheritedTypeCollection: RawUnexpectedNodesSyntax?
322+
323+
// If it is a Python style inheritance clause, then consume a right paren if there is one.
324+
if isPythonStyleInheritanceClause, let rightParen = self.consume(if: .rightParen) {
325+
unexpectedAfterInheritedTypeCollection = RawUnexpectedNodesSyntax(elements: [RawSyntax(rightParen)], arena: self.arena)
326+
} else {
327+
unexpectedAfterInheritedTypeCollection = nil
328+
}
329+
307330
return RawTypeInheritanceClauseSyntax(
308331
unexpectedBeforeColon,
309332
colon: colon,
310333
inheritedTypeCollection: RawInheritedTypeListSyntax(elements: elements, arena: self.arena),
334+
unexpectedAfterInheritedTypeCollection,
311335
arena: self.arena
312336
)
313337
}
@@ -350,3 +374,14 @@ extension Parser {
350374
)
351375
}
352376
}
377+
378+
extension Parser {
379+
private mutating func isAtPythonStyleInheritanceClause() -> Bool {
380+
guard self.at(.leftParen) else { return false }
381+
return self.withLookahead {
382+
$0.consume(if: .leftParen)
383+
guard $0.canParseType() else { return false }
384+
return $0.at(.rightParen, .keyword(.where), .leftBrace) || $0.at(.eof)
385+
}
386+
}
387+
}

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,51 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
12861286
return handleEffectSpecifiers(node)
12871287
}
12881288

1289+
public override func visit(_ node: TypeInheritanceClauseSyntax) -> SyntaxVisitorContinueKind {
1290+
if shouldSkip(node) {
1291+
return .skipChildren
1292+
}
1293+
1294+
if let unexpected = node.unexpectedBeforeColon,
1295+
let leftParen = unexpected.onlyToken(where: { $0.tokenKind == .leftParen })
1296+
{
1297+
1298+
var handledNodes: [SyntaxIdentifier] = [
1299+
leftParen.id,
1300+
node.colon.id,
1301+
]
1302+
1303+
var changes: [FixIt.MultiNodeChange] = [
1304+
.makePresent(node.colon),
1305+
.makeMissing(unexpected),
1306+
]
1307+
1308+
var replaceTokens = [leftParen]
1309+
1310+
if let rightParen = node.unexpectedAfterInheritedTypeCollection?.onlyToken(where: { $0.tokenKind == .rightParen }) {
1311+
handledNodes += [rightParen.id]
1312+
changes += [
1313+
.makeMissing(rightParen)
1314+
]
1315+
1316+
replaceTokens += [rightParen]
1317+
}
1318+
1319+
addDiagnostic(
1320+
unexpected,
1321+
.expectedColonClass,
1322+
fixIts: [
1323+
FixIt(
1324+
message: ReplaceTokensFixIt(replaceTokens: replaceTokens, replacements: [.colonToken()]),
1325+
changes: changes
1326+
)
1327+
],
1328+
handledNodes: handledNodes
1329+
)
1330+
}
1331+
return .visitChildren
1332+
}
1333+
12891334
public override func visit(_ node: TypeInitializerClauseSyntax) -> SyntaxVisitorContinueKind {
12901335
if shouldSkip(node) {
12911336
return .skipChildren

Sources/SwiftParserDiagnostics/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 escapedNewlineAtLatlineOfMultiLineStringLiteralNotAllowed: Self {
126126
.init("escaped newline at the last line of a multi-line string literal is not allowed")
127127
}
128+
public static var expectedColonClass: Self {
129+
.init("expected ':' to begin inheritance clause")
130+
}
128131
public static var expectedExpressionAfterTry: Self {
129132
.init("expected expression after 'try'")
130133
}

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,9 +1521,14 @@ final class RecoveryTests: XCTestCase {
15211521
class WrongInheritanceClause11️⃣(Int) {}
15221522
""",
15231523
diagnostics: [
1524-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 34 - 35 = ''
1525-
DiagnosticSpec(message: "unexpected code '(Int)' in class")
1526-
]
1524+
DiagnosticSpec(
1525+
message: "expected ':' to begin inheritance clause",
1526+
fixIts: ["replace '()' with ':'"]
1527+
)
1528+
],
1529+
fixedSource: """
1530+
class WrongInheritanceClause1: Int {}
1531+
"""
15271532
)
15281533
}
15291534

@@ -1533,9 +1538,14 @@ final class RecoveryTests: XCTestCase {
15331538
class WrongInheritanceClause21️⃣(Base2<Int>) {}
15341539
""",
15351540
diagnostics: [
1536-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 41 - 42 = ''
1537-
DiagnosticSpec(message: "unexpected code '(Base2<Int>)' in class")
1538-
]
1541+
DiagnosticSpec(
1542+
message: "expected ':' to begin inheritance clause",
1543+
fixIts: ["replace '()' with ':'"]
1544+
)
1545+
],
1546+
fixedSource: """
1547+
class WrongInheritanceClause2: Base2<Int>{}
1548+
"""
15391549
)
15401550
}
15411551

@@ -1545,9 +1555,14 @@ final class RecoveryTests: XCTestCase {
15451555
class WrongInheritanceClause3<T>1️⃣(SubModule.Base1) where T:AnyObject {}
15461556
""",
15471557
diagnostics: [
1548-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': ', 49 - 50 = ''
1549-
DiagnosticSpec(message: "unexpected code '(SubModule.Base1) where T:AnyObject' in class")
1550-
]
1558+
DiagnosticSpec(
1559+
message: "expected ':' to begin inheritance clause",
1560+
fixIts: ["replace '()' with ':'"]
1561+
)
1562+
],
1563+
fixedSource: """
1564+
class WrongInheritanceClause3<T>: SubModule.Base1 where T:AnyObject {}
1565+
"""
15511566
)
15521567
}
15531568

@@ -1557,9 +1572,14 @@ final class RecoveryTests: XCTestCase {
15571572
class WrongInheritanceClause41️⃣(SubModule.Base2<Int>) {}
15581573
""",
15591574
diagnostics: [
1560-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 51 - 52 = ''
1561-
DiagnosticSpec(message: "unexpected code '(SubModule.Base2<Int>)' in class")
1562-
]
1575+
DiagnosticSpec(
1576+
message: "expected ':' to begin inheritance clause",
1577+
fixIts: ["replace '()' with ':'"]
1578+
)
1579+
],
1580+
fixedSource: """
1581+
class WrongInheritanceClause4: SubModule.Base2<Int>{}
1582+
"""
15631583
)
15641584
}
15651585

@@ -1569,47 +1589,55 @@ final class RecoveryTests: XCTestCase {
15691589
class WrongInheritanceClause5<T>1️⃣(SubModule.Base2<Int>) where T:AnyObject {}
15701590
""",
15711591
diagnostics: [
1572-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': ', 54 - 55 = ''
1573-
DiagnosticSpec(message: "unexpected code '(SubModule.Base2<Int>) where T:AnyObject' in class")
1574-
]
1592+
DiagnosticSpec(
1593+
message: "expected ':' to begin inheritance clause",
1594+
fixIts: ["replace '()' with ':'"]
1595+
)
1596+
],
1597+
fixedSource: """
1598+
class WrongInheritanceClause5<T>: SubModule.Base2<Int>where T:AnyObject {}
1599+
"""
15751600
)
15761601
}
15771602

15781603
func testRecovery130() {
15791604
assertParse(
15801605
"""
1581-
class WrongInheritanceClause61️⃣(Int 2️⃣{}3️⃣
1606+
class WrongInheritanceClause61️⃣(Int {}
15821607
""",
15831608
diagnostics: [
1584-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': '
1585-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in class", fixIts: ["insert '{'"]),
1586-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'var' in variable", fixIts: ["insert 'var'"]),
1587-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end tuple pattern", fixIts: ["insert ')'"]),
1588-
DiagnosticSpec(locationMarker: "3️⃣", message: "expected '}' to end class", fixIts: ["insert '}'"]),
1589-
]
1609+
DiagnosticSpec(
1610+
message: "expected ':' to begin inheritance clause",
1611+
fixIts: ["replace '(' with ':'"]
1612+
)
1613+
],
1614+
fixedSource: """
1615+
class WrongInheritanceClause6: Int {}
1616+
"""
15901617
)
15911618
}
15921619

15931620
func testRecovery131() {
15941621
assertParse(
15951622
"""
1596-
class WrongInheritanceClause7<T>1️⃣(Int 2️⃣where T:AnyObject {}
1623+
class WrongInheritanceClause7<T>1️⃣(Int where T:AnyObject {}
15971624
""",
15981625
diagnostics: [
1599-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': '
1600-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in class", fixIts: ["insert '{'"]),
1601-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'var' in variable", fixIts: ["insert 'var'"]),
1602-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end tuple pattern", fixIts: ["insert ')'"]),
1603-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected '}' to end class", fixIts: ["insert '}'"]),
1604-
DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code 'where T:AnyObject {}' at top level"),
1605-
]
1626+
DiagnosticSpec(
1627+
message: "expected ':' to begin inheritance clause",
1628+
fixIts: ["replace '(' with ':'"]
1629+
)
1630+
],
1631+
fixedSource: """
1632+
class WrongInheritanceClause7<T>: Int where T:AnyObject {}
1633+
"""
16061634
)
16071635
}
16081636

16091637
func testRecovery132() {
1638+
// <rdar://problem/18502220> [swift-crashes 078] parser crash on invalid cast in sequence expr
16101639
assertParse(
16111640
"""
1612-
// <rdar://problem/18502220> [swift-crashes 078] parser crash on invalid cast in sequence expr
16131641
Base=1 as Base=1
16141642
"""
16151643
)

0 commit comments

Comments
 (0)