Skip to content

Commit b4d3817

Browse files
committed
Merge pull request #1450 from kimdv/kimdv/add-diagnostic-for-wrong-inheritance
Add diagnostic for wrong inheritance
1 parent 2774987 commit b4d3817

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
@@ -301,10 +314,21 @@ extension Parser {
301314
)
302315
} while keepGoing != nil && loopProgress.evaluate(currentToken)
303316
}
317+
318+
let unexpectedAfterInheritedTypeCollection: RawUnexpectedNodesSyntax?
319+
320+
// If it is a Python style inheritance clause, then consume a right paren if there is one.
321+
if isPythonStyleInheritanceClause, let rightParen = self.consume(if: .rightParen) {
322+
unexpectedAfterInheritedTypeCollection = RawUnexpectedNodesSyntax(elements: [RawSyntax(rightParen)], arena: self.arena)
323+
} else {
324+
unexpectedAfterInheritedTypeCollection = nil
325+
}
326+
304327
return RawTypeInheritanceClauseSyntax(
305328
unexpectedBeforeColon,
306329
colon: colon,
307330
inheritedTypeCollection: RawInheritedTypeListSyntax(elements: elements, arena: self.arena),
331+
unexpectedAfterInheritedTypeCollection,
308332
arena: self.arena
309333
)
310334
}
@@ -347,3 +371,14 @@ extension Parser {
347371
)
348372
}
349373
}
374+
375+
extension Parser {
376+
private mutating func isAtPythonStyleInheritanceClause() -> Bool {
377+
guard self.at(.leftParen) else { return false }
378+
return self.withLookahead {
379+
$0.consume(if: .leftParen)
380+
guard $0.canParseType() else { return false }
381+
return $0.at(.rightParen, .keyword(.where), .leftBrace) || $0.at(.eof)
382+
}
383+
}
384+
}

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,51 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11751175
return handleEffectSpecifiers(node)
11761176
}
11771177

1178+
public override func visit(_ node: TypeInheritanceClauseSyntax) -> SyntaxVisitorContinueKind {
1179+
if shouldSkip(node) {
1180+
return .skipChildren
1181+
}
1182+
1183+
if let unexpected = node.unexpectedBeforeColon,
1184+
let leftParen = unexpected.onlyToken(where: { $0.tokenKind == .leftParen })
1185+
{
1186+
1187+
var handledNodes: [SyntaxIdentifier] = [
1188+
leftParen.id,
1189+
node.colon.id,
1190+
]
1191+
1192+
var changes: [FixIt.MultiNodeChange] = [
1193+
.makePresent(node.colon),
1194+
.makeMissing(unexpected),
1195+
]
1196+
1197+
var replaceTokens = [leftParen]
1198+
1199+
if let rightParen = node.unexpectedAfterInheritedTypeCollection?.onlyToken(where: { $0.tokenKind == .rightParen }) {
1200+
handledNodes += [rightParen.id]
1201+
changes += [
1202+
.makeMissing(rightParen)
1203+
]
1204+
1205+
replaceTokens += [rightParen]
1206+
}
1207+
1208+
addDiagnostic(
1209+
unexpected,
1210+
.expectedColonClass,
1211+
fixIts: [
1212+
FixIt(
1213+
message: ReplaceTokensFixIt(replaceTokens: replaceTokens, replacements: [.colonToken()]),
1214+
changes: changes
1215+
)
1216+
],
1217+
handledNodes: handledNodes
1218+
)
1219+
}
1220+
return .visitChildren
1221+
}
1222+
11781223
public override func visit(_ node: TypeInitializerClauseSyntax) -> SyntaxVisitorContinueKind {
11791224
if shouldSkip(node) {
11801225
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
@@ -1516,9 +1516,14 @@ final class RecoveryTests: XCTestCase {
15161516
class WrongInheritanceClause11️⃣(Int) {}
15171517
""",
15181518
diagnostics: [
1519-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 34 - 35 = ''
1520-
DiagnosticSpec(message: "unexpected code '(Int)' in class")
1521-
]
1519+
DiagnosticSpec(
1520+
message: "expected ':' to begin inheritance clause",
1521+
fixIts: ["replace '()' with ':'"]
1522+
)
1523+
],
1524+
fixedSource: """
1525+
class WrongInheritanceClause1: Int {}
1526+
"""
15221527
)
15231528
}
15241529

@@ -1528,9 +1533,14 @@ final class RecoveryTests: XCTestCase {
15281533
class WrongInheritanceClause21️⃣(Base2<Int>) {}
15291534
""",
15301535
diagnostics: [
1531-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 41 - 42 = ''
1532-
DiagnosticSpec(message: "unexpected code '(Base2<Int>)' in class")
1533-
]
1536+
DiagnosticSpec(
1537+
message: "expected ':' to begin inheritance clause",
1538+
fixIts: ["replace '()' with ':'"]
1539+
)
1540+
],
1541+
fixedSource: """
1542+
class WrongInheritanceClause2: Base2<Int>{}
1543+
"""
15341544
)
15351545
}
15361546

@@ -1540,9 +1550,14 @@ final class RecoveryTests: XCTestCase {
15401550
class WrongInheritanceClause3<T>1️⃣(SubModule.Base1) where T:AnyObject {}
15411551
""",
15421552
diagnostics: [
1543-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': ', 49 - 50 = ''
1544-
DiagnosticSpec(message: "unexpected code '(SubModule.Base1) where T:AnyObject' in class")
1545-
]
1553+
DiagnosticSpec(
1554+
message: "expected ':' to begin inheritance clause",
1555+
fixIts: ["replace '()' with ':'"]
1556+
)
1557+
],
1558+
fixedSource: """
1559+
class WrongInheritanceClause3<T>: SubModule.Base1 where T:AnyObject {}
1560+
"""
15461561
)
15471562
}
15481563

@@ -1552,9 +1567,14 @@ final class RecoveryTests: XCTestCase {
15521567
class WrongInheritanceClause41️⃣(SubModule.Base2<Int>) {}
15531568
""",
15541569
diagnostics: [
1555-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 51 - 52 = ''
1556-
DiagnosticSpec(message: "unexpected code '(SubModule.Base2<Int>)' in class")
1557-
]
1570+
DiagnosticSpec(
1571+
message: "expected ':' to begin inheritance clause",
1572+
fixIts: ["replace '()' with ':'"]
1573+
)
1574+
],
1575+
fixedSource: """
1576+
class WrongInheritanceClause4: SubModule.Base2<Int>{}
1577+
"""
15581578
)
15591579
}
15601580

@@ -1564,47 +1584,55 @@ final class RecoveryTests: XCTestCase {
15641584
class WrongInheritanceClause5<T>1️⃣(SubModule.Base2<Int>) where T:AnyObject {}
15651585
""",
15661586
diagnostics: [
1567-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': ', 54 - 55 = ''
1568-
DiagnosticSpec(message: "unexpected code '(SubModule.Base2<Int>) where T:AnyObject' in class")
1569-
]
1587+
DiagnosticSpec(
1588+
message: "expected ':' to begin inheritance clause",
1589+
fixIts: ["replace '()' with ':'"]
1590+
)
1591+
],
1592+
fixedSource: """
1593+
class WrongInheritanceClause5<T>: SubModule.Base2<Int>where T:AnyObject {}
1594+
"""
15701595
)
15711596
}
15721597

15731598
func testRecovery130() {
15741599
assertParse(
15751600
"""
1576-
class WrongInheritanceClause61️⃣(Int 2️⃣{}3️⃣
1601+
class WrongInheritanceClause61️⃣(Int {}
15771602
""",
15781603
diagnostics: [
1579-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': '
1580-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in class"),
1581-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'var' in variable"),
1582-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end tuple pattern"),
1583-
DiagnosticSpec(locationMarker: "3️⃣", message: "expected '}' to end class"),
1584-
]
1604+
DiagnosticSpec(
1605+
message: "expected ':' to begin inheritance clause",
1606+
fixIts: ["replace '(' with ':'"]
1607+
)
1608+
],
1609+
fixedSource: """
1610+
class WrongInheritanceClause6: Int {}
1611+
"""
15851612
)
15861613
}
15871614

15881615
func testRecovery131() {
15891616
assertParse(
15901617
"""
1591-
class WrongInheritanceClause7<T>1️⃣(Int 2️⃣where T:AnyObject {}
1618+
class WrongInheritanceClause7<T>1️⃣(Int where T:AnyObject {}
15921619
""",
15931620
diagnostics: [
1594-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': '
1595-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in class"),
1596-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'var' in variable"),
1597-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end tuple pattern"),
1598-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected '}' to end class"),
1599-
DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code 'where T:AnyObject {}' at top level"),
1600-
]
1621+
DiagnosticSpec(
1622+
message: "expected ':' to begin inheritance clause",
1623+
fixIts: ["replace '(' with ':'"]
1624+
)
1625+
],
1626+
fixedSource: """
1627+
class WrongInheritanceClause7<T>: Int where T:AnyObject {}
1628+
"""
16011629
)
16021630
}
16031631

16041632
func testRecovery132() {
1633+
// <rdar://problem/18502220> [swift-crashes 078] parser crash on invalid cast in sequence expr
16051634
assertParse(
16061635
"""
1607-
// <rdar://problem/18502220> [swift-crashes 078] parser crash on invalid cast in sequence expr
16081636
Base=1 as Base=1
16091637
"""
16101638
)

0 commit comments

Comments
 (0)