Skip to content

Commit 9ae5fd2

Browse files
committed
Add diagnostic if self. is missing in front of init call
1 parent 8d5a984 commit 9ae5fd2

File tree

9 files changed

+123
-51
lines changed

9 files changed

+123
-51
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ extension DeclarationModifier {
3131
extension TokenConsumer {
3232
func atStartOfDeclaration(
3333
isAtTopLevel: Bool = false,
34+
allowInitDecl: Bool = true,
3435
allowRecovery: Bool = false
3536
) -> Bool {
3637
if self.at(anyIn: PoundDeclarationStart.self) != nil {
@@ -93,17 +94,19 @@ extension TokenConsumer {
9394
var lookahead = subparser.lookahead()
9495
repeat {
9596
lookahead.consumeAnyToken()
96-
} while lookahead.atStartOfDeclaration()
97+
} while lookahead.atStartOfDeclaration(allowInitDecl: allowInitDecl)
9798
return lookahead.at(.identifier)
9899
case .caseKeyword:
99100
// When 'case' appears inside a function, it's probably a switch
100101
// case, not an enum case declaration.
101102
return false
103+
case .initKeyword:
104+
return allowInitDecl
102105
case nil:
103106
if subparser.at(anyIn: ContextualDeclKeyword.self)?.0 != nil {
104107
subparser.consumeAnyToken()
105108
return subparser.atStartOfDeclaration(
106-
isAtTopLevel: isAtTopLevel, allowRecovery: allowRecovery)
109+
isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: allowRecovery)
107110
}
108111
return false
109112
default: return true
@@ -1283,7 +1286,7 @@ extension Parser {
12831286
whereClause = nil
12841287
}
12851288

1286-
let items = self.parseOptionalCodeBlock()
1289+
let items = self.parseOptionalCodeBlock(allowInitDecl: false)
12871290

12881291
return RawInitializerDeclSyntax(
12891292
attributes: attrs.attributes,

Sources/SwiftParser/Diagnostics/MissingNodesError.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,15 @@ public struct MissingNodesError: ParserError {
216216
/// If applicable, returns a string that describes the node in which the missing nodes are expected.
217217
private func parentContextClause(anchor: Syntax?) -> String? {
218218
// anchorParent is the first parent that has a type name for diagnostics.
219-
guard let anchorParent = anchor?.ancestorOrSelf(where: {
220-
$0.nodeTypeNameForDiagnostics(allowBlockNames: false) != nil
219+
guard let (anchorParent, anchorTypeName) = anchor?.ancestorOrSelf(mapping: { (node: Syntax) -> (Syntax, String)? in
220+
if let name = node.nodeTypeNameForDiagnostics(allowBlockNames: false) {
221+
return (node, name)
222+
} else {
223+
return nil
224+
}
221225
}) else {
222226
return nil
223227
}
224-
let anchorTypeName = anchorParent.nodeTypeNameForDiagnostics(allowBlockNames: false)!
225228
if anchorParent.is(SourceFileSyntax.self) {
226229
return nil
227230
}

Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift

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

532+
public override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind {
533+
if shouldSkip(node) {
534+
return .skipChildren
535+
}
536+
if let base = node.base?.as(IdentifierExprSyntax.self)?.identifier,
537+
base.presence == .missing,
538+
base.tokenKind == .selfKeyword,
539+
node.dot.presence == .missing,
540+
node.name.tokenKind == .identifier("init") {
541+
var replacement = PresentMaker().visit(base).as(TokenSyntax.self)!
542+
replacement.leadingTrivia = node.name.leadingTrivia
543+
544+
var addSuper = false
545+
if let initDecl = node.ancestorOrSelf(mapping: { $0.as(InitializerDeclSyntax.self )}),
546+
initDecl.parent?.ancestorOrSelf(mapping: { $0.as(DeclSyntax.self) })?.is(ClassDeclSyntax.self) ?? false,
547+
!(initDecl.modifiers?.contains(where: { $0.name.tokenKind == .contextualKeyword("convenience") }) ?? false) {
548+
addSuper = true
549+
replacement = TokenSyntax(
550+
.superKeyword,
551+
leadingTrivia: replacement.leadingTrivia,
552+
trailingTrivia: replacement.trailingTrivia,
553+
presence: .present
554+
)
555+
}
556+
557+
addDiagnostic(node.name, addSuper ? .missingSuperInInitializerInvocation : .missingSelfInInitializerInvocation, fixIts: [
558+
FixIt(message: InsertTokenFixIt(missingNodes: [Syntax(replacement), Syntax(node.dot)]), changes: [
559+
[.replace(oldNode: Syntax(base), newNode: Syntax(replacement)), .replaceLeadingTrivia(token: node.name, newTrivia: [])],
560+
.makePresent(node.dot),
561+
])
562+
], handledNodes: [base.id, node.dot.id])
563+
}
564+
return .visitChildren
565+
}
566+
532567
public override func visit(_ node: PrecedenceGroupAssignmentSyntax) -> SyntaxVisitorContinueKind {
533568
if shouldSkip(node) {
534569
return .skipChildren

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ extension DiagnosticMessage where Self == StaticParserError {
134134
public static var missingColonInTernaryExpr: Self {
135135
.init("expected ':' after '? ...' in ternary expression")
136136
}
137+
public static var missingSelfInInitializerInvocation: Self {
138+
.init("missing 'self.' at initializer invocation")
139+
}
140+
public static var missingSuperInInitializerInvocation: Self {
141+
.init("missing 'super.' at initializer invocation")
142+
}
137143
public static var operatorShouldBeDeclaredWithoutBody: Self {
138144
.init("operator should not be declared with body")
139145
}
@@ -221,10 +227,10 @@ public struct SpaceSeparatedIdentifiersError: ParserError {
221227
public let additionalTokens: [TokenSyntax]
222228

223229
public var message: String {
224-
if let anchorParent = firstToken.parent?.ancestorOrSelf(where: {
225-
$0.nodeTypeNameForDiagnostics(allowBlockNames: false) != nil
230+
if let name = firstToken.parent?.ancestorOrSelf(mapping: {
231+
$0.nodeTypeNameForDiagnostics(allowBlockNames: false)
226232
}) {
227-
return "found an unexpected second identifier in \(anchorParent.nodeTypeNameForDiagnostics(allowBlockNames: false)!)"
233+
return "found an unexpected second identifier in \(name)"
228234
} else {
229235
return "found an unexpected second identifier"
230236
}
@@ -263,7 +269,7 @@ public struct UnexpectedNodesError: ParserError {
263269
if let parent = unexpectedNodes.parent {
264270
if let parentTypeName = parent.nodeTypeNameForDiagnostics(allowBlockNames: false), parent.children(viewMode: .sourceAccurate).first?.id == unexpectedNodes.id {
265271
message += " before \(parentTypeName)"
266-
} else if let parentTypeName = parent.ancestorOrSelf(where: { $0.nodeTypeNameForDiagnostics(allowBlockNames: false) != nil })?.nodeTypeNameForDiagnostics(allowBlockNames: false) {
272+
} else if let parentTypeName = parent.ancestorOrSelf(mapping: { $0.nodeTypeNameForDiagnostics(allowBlockNames: false) }) {
267273
message += " in \(parentTypeName)"
268274
}
269275
}

Sources/SwiftParser/Diagnostics/SyntaxExtensions.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,15 @@ extension SyntaxProtocol {
9292

9393
/// Returns this node or the first ancestor that satisfies `condition`.
9494
func ancestorOrSelf(where condition: (Syntax) -> Bool) -> Syntax? {
95+
return ancestorOrSelf(mapping: { condition($0) ? $0 : nil })
96+
}
97+
98+
/// Returns this node or the first ancestor that satisfies `condition`.
99+
func ancestorOrSelf<T>(mapping map: (Syntax) -> T?) -> T? {
95100
var walk: Syntax? = Syntax(self)
96101
while let unwrappedParent = walk {
97-
if condition(unwrappedParent) {
98-
return walk
102+
if let mapped = map(unwrappedParent) {
103+
return mapped
99104
}
100105
walk = unwrappedParent.parent
101106
}

Sources/SwiftParser/Expressions.swift

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
@_spi(RawSyntax) import SwiftSyntax
1414

1515
extension TokenConsumer {
16-
func atStartOfExpression() -> Bool {
16+
func atStartOfExpression(allowRecovery: Bool = false) -> Bool {
1717
switch self.at(anyIn: ExpressionStart.self) {
1818
case (.awaitTryMove, let handle)?:
1919
var backtrack = self.lookahead()
@@ -38,6 +38,10 @@ extension TokenConsumer {
3838
return true
3939
}
4040
}
41+
if allowRecovery && self.at(.initKeyword) {
42+
// The developer forgot to write `self.` or `super.` in front of an `init` call.
43+
return true
44+
}
4145
return false
4246
}
4347
}
@@ -513,12 +517,13 @@ extension Parser {
513517

514518
@_spi(RawSyntax)
515519
public mutating func parseDottedExpressionSuffix() -> (
516-
period: RawTokenSyntax, name: RawTokenSyntax,
517-
declNameArgs: RawDeclNameArgumentsSyntax?,
518-
generics: RawGenericArgumentClauseSyntax?
520+
unexpectedBeforePeriod: RawUnexpectedNodesSyntax?,
521+
period: RawTokenSyntax,
522+
name: RawTokenSyntax,
523+
declNameArgs: RawDeclNameArgumentsSyntax?,
524+
generics: RawGenericArgumentClauseSyntax?
519525
) {
520-
assert(self.at(any: [.period, .prefixPeriod]))
521-
let period = self.consumeAnyToken(remapping: .period)
526+
let (unexpectedBeforePeriod, period) = self.expectAny([.period, .prefixPeriod], default: .period)
522527

523528
// Parse the name portion.
524529
let name: RawTokenSyntax
@@ -545,15 +550,15 @@ extension Parser {
545550
generics = nil
546551
}
547552

548-
return (period, name, declNameArgs, generics)
553+
return (unexpectedBeforePeriod, period, name, declNameArgs, generics)
549554
}
550555

551556
@_spi(RawSyntax)
552557
public mutating func parseDottedExpressionSuffix(_ start: RawExprSyntax?) -> RawExprSyntax {
553-
let (period, name, declNameArgs, generics) = parseDottedExpressionSuffix()
558+
let (unexpectedBeforePeriod, period, name, declNameArgs, generics) = parseDottedExpressionSuffix()
554559

555560
let memberAccess = RawMemberAccessExprSyntax(
556-
base: start, dot: period, name: name, declNameArguments: declNameArgs,
561+
base: start, unexpectedBeforePeriod, dot: period, name: name, declNameArguments: declNameArgs,
557562
arena: self.arena)
558563

559564
guard let generics = generics else {
@@ -933,8 +938,9 @@ extension Parser {
933938

934939
// Check for a .name or .1 suffix.
935940
if self.at(any: [.period, .prefixPeriod]) {
936-
let (period, name, declNameArgs, generics) = parseDottedExpressionSuffix()
941+
let (unexpectedBeforePeriod, period, name, declNameArgs, generics) = parseDottedExpressionSuffix()
937942
components.append(RawKeyPathComponentSyntax(
943+
unexpectedBeforePeriod,
938944
period: period,
939945
component: RawSyntax(RawKeyPathPropertyComponentSyntax(
940946
identifier: name, declNameArguments: declNameArgs,
@@ -1148,6 +1154,13 @@ extension Parser {
11481154
case (.leftSquareBracket, _)?:
11491155
return self.parseCollectionLiteral()
11501156

1157+
case (.initKeyword, _)?:
1158+
let synthesizedBase = RawExprSyntax(RawIdentifierExprSyntax(
1159+
identifier: missingToken(.selfKeyword, text: nil),
1160+
declNameArguments: nil,
1161+
arena: self.arena
1162+
))
1163+
return parseDottedExpressionSuffix(synthesizedBase)
11511164
case nil:
11521165
return RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
11531166
}

Sources/SwiftParser/RawTokenKindSubset.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
731731
case falseKeyword
732732
case floatingLiteral
733733
case identifier
734+
case initKeyword // For recovery if the user forgot `self.`
734735
case integerLiteral
735736
case leftBrace
736737
case leftParen
@@ -771,6 +772,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
771772
case .falseKeyword: self = .falseKeyword
772773
case .floatingLiteral: self = .floatingLiteral
773774
case .identifier: self = .identifier
775+
case .initKeyword: self = .initKeyword
774776
case .integerLiteral: self = .integerLiteral
775777
case .leftBrace: self = .leftBrace
776778
case .leftParen: self = .leftParen
@@ -814,6 +816,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
814816
case .falseKeyword: return .falseKeyword
815817
case .floatingLiteral: return .floatingLiteral
816818
case .identifier: return .identifier
819+
case .initKeyword: return .initKeyword
817820
case .integerLiteral: return .integerLiteral
818821
case .leftBrace: return .leftBrace
819822
case .leftParen: return .leftParen

Sources/SwiftParser/TopLevel.swift

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ extension Parser {
3737
}
3838

3939
extension Parser {
40-
mutating func parseCodeBlockItemList(isAtTopLevel: Bool, stopCondition: (inout Parser) -> Bool) -> RawCodeBlockItemListSyntax {
40+
mutating func parseCodeBlockItemList(isAtTopLevel: Bool, allowInitDecl: Bool = true, stopCondition: (inout Parser) -> Bool) -> RawCodeBlockItemListSyntax {
4141
var elements = [RawCodeBlockItemSyntax]()
4242
var loopProgress = LoopProgressCondition()
4343
while !stopCondition(&self), loopProgress.evaluate(currentToken) {
4444
let newItemAtStartOfLine = self.currentToken.isAtStartOfLine
45-
guard let newElement = self.parseCodeBlockItem(isAtTopLevel: isAtTopLevel) else {
45+
guard let newElement = self.parseCodeBlockItem(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl) else {
4646
break
4747
}
4848
if let lastItem = elements.last, lastItem.semicolon == nil && !newItemAtStartOfLine {
@@ -68,11 +68,11 @@ extension Parser {
6868
///
6969
/// This function is used when parsing places where function bodies are
7070
/// optional - like the function requirements in protocol declarations.
71-
mutating func parseOptionalCodeBlock() -> RawCodeBlockSyntax? {
71+
mutating func parseOptionalCodeBlock(allowInitDecl: Bool = true) -> RawCodeBlockSyntax? {
7272
guard self.at(.leftBrace) else {
7373
return nil
7474
}
75-
return self.parseCodeBlock()
75+
return self.parseCodeBlock(allowInitDecl: allowInitDecl)
7676
}
7777

7878
/// Parse a code block.
@@ -85,9 +85,9 @@ extension Parser {
8585
/// `introducer` is the `while`, `if`, ... keyword that is the cause that the code block is being parsed.
8686
/// If the left brace is missing, its indentation will be used to judge whether a following `}` was
8787
/// indented to close this code block or a surrounding context. See `expectRightBrace`.
88-
mutating func parseCodeBlock(introducer: RawTokenSyntax? = nil) -> RawCodeBlockSyntax {
88+
mutating func parseCodeBlock(introducer: RawTokenSyntax? = nil, allowInitDecl: Bool = true) -> RawCodeBlockSyntax {
8989
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
90-
let itemList = parseCodeBlockItemList(isAtTopLevel: false, stopCondition: { $0.at(.rightBrace) })
90+
let itemList = parseCodeBlockItemList(isAtTopLevel: false, allowInitDecl: allowInitDecl, stopCondition: { $0.at(.rightBrace) })
9191
let (unexpectedBeforeRBrace, rbrace) = self.expectRightBrace(leftBrace: lbrace, introducer: introducer)
9292

9393
return .init(
@@ -118,7 +118,7 @@ extension Parser {
118118
/// statement → compiler-control-statement
119119
/// statements → statement statements?
120120
@_spi(RawSyntax)
121-
public mutating func parseCodeBlockItem(isAtTopLevel: Bool = false) -> RawCodeBlockItemSyntax? {
121+
public mutating func parseCodeBlockItem(isAtTopLevel: Bool = false, allowInitDecl: Bool = true) -> RawCodeBlockItemSyntax? {
122122
if self.at(any: [.caseKeyword, .defaultKeyword]) {
123123
// 'case' and 'default' are invalid in code block items.
124124
// Parse them and put them in their own CodeBlockItem but as an unexpected node.
@@ -134,7 +134,7 @@ extension Parser {
134134

135135
// FIXME: It is unfortunate that the Swift book refers to these as
136136
// "statements" and not "items".
137-
let item = self.parseItem(isAtTopLevel: isAtTopLevel)
137+
let item = self.parseItem(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl)
138138
let semi = self.consume(if: .semicolon)
139139
var trailingSemis: [RawTokenSyntax] = []
140140
while let trailingSemi = self.consume(if: .semicolon) {
@@ -158,7 +158,7 @@ extension Parser {
158158
/// closing braces while trying to recover to the next item.
159159
/// If we are not at the top level, such a closing brace should close the
160160
/// wrapping declaration instead of being consumed by lookeahead.
161-
private mutating func parseItem(isAtTopLevel: Bool = false) -> RawSyntax {
161+
private mutating func parseItem(isAtTopLevel: Bool = false, allowInitDecl: Bool = true) -> RawSyntax {
162162
if self.at(.poundIfKeyword) {
163163
let directive = self.parsePoundIfDirective {
164164
$0.parseCodeBlockItem()
@@ -176,16 +176,18 @@ extension Parser {
176176
return RawSyntax(self.parsePoundLineDirective())
177177
} else if self.at(.poundSourceLocationKeyword) {
178178
return RawSyntax(self.parsePoundSourceLocationDirective())
179-
} else if self.atStartOfDeclaration() {
179+
} else if self.atStartOfDeclaration(allowInitDecl: allowInitDecl) {
180180
return RawSyntax(self.parseDeclaration())
181181
} else if self.atStartOfStatement() {
182182
return RawSyntax(self.parseStatement())
183183
} else if self.atStartOfExpression() {
184184
return RawSyntax(self.parseExpression())
185-
} else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowRecovery: true) {
185+
} else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: true) {
186186
return RawSyntax(self.parseDeclaration())
187187
} else if self.atStartOfStatement(allowRecovery: true) {
188188
return RawSyntax(self.parseStatement())
189+
} else if self.atStartOfExpression(allowRecovery: true) {
190+
return RawSyntax(self.parseExpression())
189191
} else {
190192
return RawSyntax(RawMissingExprSyntax(arena: self.arena))
191193
}

0 commit comments

Comments
 (0)