Skip to content

Commit df0eaca

Browse files
committed
Add diagnostic for wrong inheritance
1 parent c2734b5 commit df0eaca

File tree

5 files changed

+124
-34
lines changed

5 files changed

+124
-34
lines changed

Sources/SwiftParser/Nominals.swift

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,13 @@ extension Parser {
180180
}
181181

182182
let inheritance: RawTypeInheritanceClauseSyntax?
183-
if self.at(.colon) {
183+
if self.at(.colon)
184+
|| (self.at(.leftParen)
185+
&& self.withLookahead {
186+
$0.consume(if: .leftParen)
187+
return $0.canParseType() && ($0.at(.rightParen, .keyword(.where), .leftBrace) || $0.at(.eof))
188+
})
189+
{
184190
inheritance = self.parseInheritance()
185191
} else {
186192
inheritance = nil
@@ -213,7 +219,17 @@ extension Parser {
213219
/// Parse an inheritance clause.
214220
@_spi(RawSyntax)
215221
public mutating func parseInheritance() -> RawTypeInheritanceClauseSyntax {
216-
let (unexpectedBeforeColon, colon) = self.expect(.colon)
222+
let unexpectedBeforeColon: RawUnexpectedNodesSyntax?
223+
let colon: RawTokenSyntax
224+
225+
if self.at(.colon) {
226+
unexpectedBeforeColon = nil
227+
colon = consumeAnyToken(remapping: .colon)
228+
} else {
229+
unexpectedBeforeColon = RawUnexpectedNodesSyntax([self.consumeAnyToken()], arena: self.arena)
230+
colon = missingToken(.colon)
231+
}
232+
217233
var elements = [RawInheritedTypeSyntax]()
218234
do {
219235
var keepGoing: RawTokenSyntax? = nil
@@ -246,10 +262,20 @@ extension Parser {
246262
)
247263
} while keepGoing != nil && loopProgress.evaluate(currentToken)
248264
}
265+
266+
let unexpectedAfterInheritedTypeCollection: RawUnexpectedNodesSyntax?
267+
268+
if let rightBrace = self.consume(if: .rightParen) {
269+
unexpectedAfterInheritedTypeCollection = RawUnexpectedNodesSyntax(elements: [RawSyntax(rightBrace)], arena: self.arena)
270+
} else {
271+
unexpectedAfterInheritedTypeCollection = nil
272+
}
273+
249274
return RawTypeInheritanceClauseSyntax(
250275
unexpectedBeforeColon,
251276
colon: colon,
252277
inheritedTypeCollection: RawInheritedTypeListSyntax(elements: elements, arena: self.arena),
278+
unexpectedAfterInheritedTypeCollection,
253279
arena: self.arena
254280
)
255281
}

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,50 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
12181218
return handleEffectSpecifiers(node)
12191219
}
12201220

1221+
public override func visit(_ node: TypeInheritanceClauseSyntax) -> SyntaxVisitorContinueKind {
1222+
if shouldSkip(node) {
1223+
return .skipChildren
1224+
}
1225+
1226+
if let unexpected = node.unexpectedBeforeColon,
1227+
let leftParen = unexpected.tokens(satisfying: { $0.tokenKind == .leftParen }).first
1228+
{
1229+
1230+
var handledNodes: [SyntaxIdentifier] = [
1231+
leftParen.id, node.colon.id,
1232+
]
1233+
1234+
var changes: [FixIt.MultiNodeChange] = [
1235+
.makePresent(node.colon, trailingTrivia: .space),
1236+
.makeMissing(unexpected),
1237+
]
1238+
1239+
if let rightParen = node.unexpectedAfterInheritedTypeCollection?.tokens(satisfying: { $0.tokenKind == .rightParen }).first,
1240+
let lastToken = node.inheritedTypeCollection.lastToken(viewMode: .sourceAccurate)
1241+
{
1242+
handledNodes += [rightParen.id]
1243+
changes += [
1244+
.makeMissing(rightParen),
1245+
FixIt.MultiNodeChange(.replaceTrailingTrivia(token: lastToken, newTrivia: .space)),
1246+
]
1247+
}
1248+
1249+
addDiagnostic(
1250+
unexpected,
1251+
.expectedColonClass,
1252+
fixIts: [
1253+
FixIt(
1254+
message: ReplaceTokensFixIt(replaceTokens: [], replacement: .colonToken()),
1255+
changes: changes
1256+
)
1257+
],
1258+
handledNodes: handledNodes
1259+
)
1260+
}
1261+
1262+
return .visitChildren
1263+
}
1264+
12211265
public override func visit(_ node: TypeInitializerClauseSyntax) -> SyntaxVisitorContinueKind {
12221266
if shouldSkip(node) {
12231267
return .skipChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 13 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
}
@@ -614,3 +617,13 @@ public struct ReplaceTokensFixIt: ParserFixIt {
614617
"replace \(nodesDescription(replaceTokens, format: false)) with '\(replacement.text)'"
615618
}
616619
}
620+
621+
public struct TypeInheritanceClauseSyntaxFixIt: ParserFixIt {
622+
public let node: TypeInheritanceClauseSyntax
623+
624+
public let replacement: TokenSyntax
625+
626+
public var message: String {
627+
"replace \(node.description) with '\(replacement.text)'"
628+
}
629+
}

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ final class DeclarationTests: XCTestCase {
850850
DiagnosticSpec(locationMarker: "1️⃣", message: "expected ':' in parameter"),
851851
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end parameter clause"),
852852
DiagnosticSpec(locationMarker: "3️⃣", message: "expected identifier in struct"),
853-
DiagnosticSpec(locationMarker: "4️⃣", message: "unexpected code ')' in struct"),
853+
DiagnosticSpec(locationMarker: "4️⃣", message: "unexpected code ')' in inheritance clause"),
854854
]
855855
)
856856
}

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,9 +1551,11 @@ final class RecoveryTests: XCTestCase {
15511551
class WrongInheritanceClause11️⃣(Int) {}
15521552
""",
15531553
diagnostics: [
1554-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 34 - 35 = ''
1555-
DiagnosticSpec(message: "unexpected code '(Int)' in class")
1556-
]
1554+
DiagnosticSpec(message: "expected ':' to begin inheritance clause")
1555+
],
1556+
fixedSource: """
1557+
class WrongInheritanceClause1: Int {}
1558+
"""
15571559
)
15581560
}
15591561

@@ -1563,9 +1565,11 @@ final class RecoveryTests: XCTestCase {
15631565
class WrongInheritanceClause21️⃣(Base2<Int>) {}
15641566
""",
15651567
diagnostics: [
1566-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 41 - 42 = ''
1567-
DiagnosticSpec(message: "unexpected code '(Base2<Int>)' in class")
1568-
]
1568+
DiagnosticSpec(message: "expected ':' to begin inheritance clause")
1569+
],
1570+
fixedSource: """
1571+
class WrongInheritanceClause2: Base2<Int> {}
1572+
"""
15691573
)
15701574
}
15711575

@@ -1575,9 +1579,11 @@ final class RecoveryTests: XCTestCase {
15751579
class WrongInheritanceClause3<T>1️⃣(SubModule.Base1) where T:AnyObject {}
15761580
""",
15771581
diagnostics: [
1578-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': ', 49 - 50 = ''
1579-
DiagnosticSpec(message: "unexpected code '(SubModule.Base1) where T:AnyObject' in class")
1580-
]
1582+
DiagnosticSpec(message: "expected ':' to begin inheritance clause")
1583+
],
1584+
fixedSource: """
1585+
class WrongInheritanceClause3<T>: SubModule.Base1 where T:AnyObject {}
1586+
"""
15811587
)
15821588
}
15831589

@@ -1587,9 +1593,11 @@ final class RecoveryTests: XCTestCase {
15871593
class WrongInheritanceClause41️⃣(SubModule.Base2<Int>) {}
15881594
""",
15891595
diagnostics: [
1590-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': ', 51 - 52 = ''
1591-
DiagnosticSpec(message: "unexpected code '(SubModule.Base2<Int>)' in class")
1592-
]
1596+
DiagnosticSpec(message: "expected ':' to begin inheritance clause")
1597+
],
1598+
fixedSource: """
1599+
class WrongInheritanceClause4: SubModule.Base2<Int> {}
1600+
"""
15931601
)
15941602
}
15951603

@@ -1599,47 +1607,46 @@ final class RecoveryTests: XCTestCase {
15991607
class WrongInheritanceClause5<T>1️⃣(SubModule.Base2<Int>) where T:AnyObject {}
16001608
""",
16011609
diagnostics: [
1602-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': ', 54 - 55 = ''
1603-
DiagnosticSpec(message: "unexpected code '(SubModule.Base2<Int>) where T:AnyObject' in class")
1604-
]
1610+
DiagnosticSpec(message: "expected ':' to begin inheritance clause")
1611+
],
1612+
fixedSource: """
1613+
class WrongInheritanceClause5<T>: SubModule.Base2<Int> where T:AnyObject {}
1614+
"""
16051615
)
16061616
}
16071617

16081618
func testRecovery130() {
16091619
assertParse(
16101620
"""
1611-
class WrongInheritanceClause61️⃣(Int 2️⃣{}3️⃣
1621+
class WrongInheritanceClause61️⃣(Int {}
16121622
""",
16131623
diagnostics: [
1614-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 30 - 31 = ': '
1615-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in class"),
1616-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'var' in variable"),
1617-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end tuple pattern"),
1618-
DiagnosticSpec(locationMarker: "3️⃣", message: "expected '}' to end class"),
1619-
]
1624+
DiagnosticSpec(message: "expected ':' to begin inheritance clause")
1625+
],
1626+
fixedSource: """
1627+
class WrongInheritanceClause6: Int {}
1628+
"""
16201629
)
16211630
}
16221631

16231632
func testRecovery131() {
16241633
assertParse(
16251634
"""
1626-
class WrongInheritanceClause7<T>1️⃣(Int 2️⃣where T:AnyObject {}
1635+
class WrongInheritanceClause7<T>1️⃣(Int where T:AnyObject {}
16271636
""",
16281637
diagnostics: [
1629-
// TODO: Old parser expected error on line 1: expected ':' to begin inheritance clause, Fix-It replacements: 33 - 34 = ': '
1630-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in class"),
1631-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'var' in variable"),
1632-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end tuple pattern"),
1633-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected '}' to end class"),
1634-
DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code 'where T:AnyObject {}' at top level"),
1635-
]
1638+
DiagnosticSpec(message: "expected ':' to begin inheritance clause")
1639+
],
1640+
fixedSource: """
1641+
class WrongInheritanceClause7<T>: Int where T:AnyObject {}
1642+
"""
16361643
)
16371644
}
16381645

16391646
func testRecovery132() {
1647+
// <rdar://problem/18502220> [swift-crashes 078] parser crash on invalid cast in sequence expr
16401648
assertParse(
16411649
"""
1642-
// <rdar://problem/18502220> [swift-crashes 078] parser crash on invalid cast in sequence expr
16431650
Base=1 as Base=1
16441651
"""
16451652
)

0 commit comments

Comments
 (0)