Skip to content

Allow calls to init without leading self. or super. #962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ extension DeclarationModifier {
extension TokenConsumer {
func atStartOfDeclaration(
isAtTopLevel: Bool = false,
allowInitDecl: Bool = true,
allowRecovery: Bool = false
) -> Bool {
if self.at(anyIn: PoundDeclarationStart.self) != nil {
Expand Down Expand Up @@ -93,20 +94,22 @@ extension TokenConsumer {
var lookahead = subparser.lookahead()
repeat {
lookahead.consumeAnyToken()
} while lookahead.atStartOfDeclaration()
} while lookahead.atStartOfDeclaration(allowInitDecl: allowInitDecl)
return lookahead.at(.identifier)
case .caseKeyword:
// When 'case' appears inside a function, it's probably a switch
// case, not an enum case declaration.
return false
case .initKeyword:
return allowInitDecl
case .some(_):
// All other decl start keywords unconditonally start a decl.
return true
case nil:
if subparser.at(anyIn: ContextualDeclKeyword.self)?.0 != nil {
subparser.consumeAnyToken()
return subparser.atStartOfDeclaration(
isAtTopLevel: isAtTopLevel, allowRecovery: allowRecovery)
isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: allowRecovery)
}
return false
}
Expand Down Expand Up @@ -1310,7 +1313,7 @@ extension Parser {
whereClause = nil
}

let items = self.parseOptionalCodeBlock()
let items = self.parseOptionalCodeBlock(allowInitDecl: false)

return RawInitializerDeclSyntax(
attributes: attrs.attributes,
Expand Down
9 changes: 6 additions & 3 deletions Sources/SwiftParser/Diagnostics/MissingNodesError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,15 @@ public struct MissingNodesError: ParserError {
/// If applicable, returns a string that describes the node in which the missing nodes are expected.
private func parentContextClause(anchor: Syntax?) -> String? {
// anchorParent is the first parent that has a type name for diagnostics.
guard let anchorParent = anchor?.ancestorOrSelf(where: {
$0.nodeTypeNameForDiagnostics(allowBlockNames: false) != nil
guard let (anchorParent, anchorTypeName) = anchor?.ancestorOrSelf(mapping: { (node: Syntax) -> (Syntax, String)? in
if let name = node.nodeTypeNameForDiagnostics(allowBlockNames: false) {
return (node, name)
} else {
return nil
}
}) else {
return nil
}
let anchorTypeName = anchorParent.nodeTypeNameForDiagnostics(allowBlockNames: false)!
if anchorParent.is(SourceFileSyntax.self) {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,10 @@ public struct SpaceSeparatedIdentifiersError: ParserError {
public let additionalTokens: [TokenSyntax]

public var message: String {
if let anchorParent = firstToken.parent?.ancestorOrSelf(where: {
$0.nodeTypeNameForDiagnostics(allowBlockNames: false) != nil
if let name = firstToken.parent?.ancestorOrSelf(mapping: {
$0.nodeTypeNameForDiagnostics(allowBlockNames: false)
}) {
return "found an unexpected second identifier in \(anchorParent.nodeTypeNameForDiagnostics(allowBlockNames: false)!)"
return "found an unexpected second identifier in \(name)"
} else {
return "found an unexpected second identifier"
}
Expand Down Expand Up @@ -266,7 +266,7 @@ public struct UnexpectedNodesError: ParserError {
if let parent = unexpectedNodes.parent {
if let parentTypeName = parent.nodeTypeNameForDiagnostics(allowBlockNames: false), parent.children(viewMode: .sourceAccurate).first?.id == unexpectedNodes.id {
message += " before \(parentTypeName)"
} else if let parentTypeName = parent.ancestorOrSelf(where: { $0.nodeTypeNameForDiagnostics(allowBlockNames: false) != nil })?.nodeTypeNameForDiagnostics(allowBlockNames: false) {
} else if let parentTypeName = parent.ancestorOrSelf(mapping: { $0.nodeTypeNameForDiagnostics(allowBlockNames: false) }) {
message += " in \(parentTypeName)"
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ extension Parser {
poundDsohandle: tok,
arena: self.arena
))
case (.identifier, let handle)?, (.selfKeyword, let handle)?:
case (.identifier, let handle)?, (.selfKeyword, let handle)?, (.initKeyword, let handle)?:
// If we have "case let x." or "case let x(", we parse x as a normal
// name, not a binding, because it is the start of an enum pattern or
// call pattern.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Names.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ extension Parser {
mutating func parseDeclNameRef(_ flags: DeclNameOptions = []) -> (RawTokenSyntax, RawDeclNameArgumentsSyntax?) {
// Consume the base name.
let ident: RawTokenSyntax
if self.at(.identifier) || self.at(any: [.selfKeyword, .capitalSelfKeyword]) {
if self.at(.identifier) || self.at(any: [.selfKeyword, .capitalSelfKeyword, .initKeyword]) {
ident = self.expectIdentifierWithoutRecovery()
} else if flags.contains(.operators), let (_, _) = self.at(anyIn: Operator.self) {
ident = self.consumeAnyToken(remapping: .unspacedBinaryOperator)
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftParser/RawTokenKindSubset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,15 @@ enum IdentifierTokens: RawTokenKindSubset {
case anyKeyword
case capitalSelfKeyword
case identifier
case initKeyword
case selfKeyword

init?(lexeme: Lexer.Lexeme) {
switch lexeme.tokenKind {
case .anyKeyword: self = .anyKeyword
case .capitalSelfKeyword: self = .capitalSelfKeyword
case .identifier: self = .identifier
case .initKeyword: self = .initKeyword
case .selfKeyword: self = .selfKeyword
default: return nil
}
Expand All @@ -460,6 +462,7 @@ enum IdentifierTokens: RawTokenKindSubset {
case .anyKeyword: return .anyKeyword
case .capitalSelfKeyword: return .capitalSelfKeyword
case .identifier: return .identifier
case .initKeyword: return .initKeyword
case .selfKeyword: return .selfKeyword
}
}
Expand Down Expand Up @@ -743,6 +746,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
case falseKeyword
case floatingLiteral
case identifier
case initKeyword
case integerLiteral
case leftBrace
case leftParen
Expand Down Expand Up @@ -773,6 +777,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
case .falseKeyword: self = .falseKeyword
case .floatingLiteral: self = .floatingLiteral
case .identifier: self = .identifier
case .initKeyword: self = .initKeyword
case .integerLiteral: self = .integerLiteral
case .leftBrace: self = .leftBrace
case .leftParen: self = .leftParen
Expand Down Expand Up @@ -806,6 +811,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
case .falseKeyword: return .falseKeyword
case .floatingLiteral: return .floatingLiteral
case .identifier: return .identifier
case .initKeyword: return .initKeyword
case .integerLiteral: return .integerLiteral
case .leftBrace: return .leftBrace
case .leftParen: return .leftParen
Expand Down
22 changes: 11 additions & 11 deletions Sources/SwiftParser/TopLevel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ extension Parser {
}

extension Parser {
mutating func parseCodeBlockItemList(isAtTopLevel: Bool, stopCondition: (inout Parser) -> Bool) -> RawCodeBlockItemListSyntax {
mutating func parseCodeBlockItemList(isAtTopLevel: Bool, allowInitDecl: Bool = true, stopCondition: (inout Parser) -> Bool) -> RawCodeBlockItemListSyntax {
var elements = [RawCodeBlockItemSyntax]()
var loopProgress = LoopProgressCondition()
while !stopCondition(&self), loopProgress.evaluate(currentToken) {
let newItemAtStartOfLine = self.currentToken.isAtStartOfLine
guard let newElement = self.parseCodeBlockItem(isAtTopLevel: isAtTopLevel) else {
guard let newElement = self.parseCodeBlockItem(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl) else {
break
}
if let lastItem = elements.last, lastItem.semicolon == nil && !newItemAtStartOfLine {
Expand All @@ -68,11 +68,11 @@ extension Parser {
///
/// This function is used when parsing places where function bodies are
/// optional - like the function requirements in protocol declarations.
mutating func parseOptionalCodeBlock() -> RawCodeBlockSyntax? {
mutating func parseOptionalCodeBlock(allowInitDecl: Bool = true) -> RawCodeBlockSyntax? {
guard self.at(.leftBrace) else {
return nil
}
return self.parseCodeBlock()
return self.parseCodeBlock(allowInitDecl: allowInitDecl)
}

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

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

// FIXME: It is unfortunate that the Swift book refers to these as
// "statements" and not "items".
let item = self.parseItem(isAtTopLevel: isAtTopLevel)
let item = self.parseItem(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl)
let semi = self.consume(if: .semicolon)
var trailingSemis: [RawTokenSyntax] = []
while let trailingSemi = self.consume(if: .semicolon) {
Expand All @@ -158,7 +158,7 @@ extension Parser {
/// closing braces while trying to recover to the next item.
/// If we are not at the top level, such a closing brace should close the
/// wrapping declaration instead of being consumed by lookeahead.
private mutating func parseItem(isAtTopLevel: Bool = false) -> RawSyntax {
private mutating func parseItem(isAtTopLevel: Bool = false, allowInitDecl: Bool = true) -> RawSyntax {
if self.at(.poundIfKeyword) {
let directive = self.parsePoundIfDirective {
$0.parseCodeBlockItem()
Expand All @@ -174,13 +174,13 @@ extension Parser {
return RawSyntax(directive)
} else if self.at(.poundSourceLocationKeyword) {
return RawSyntax(self.parsePoundSourceLocationDirective())
} else if self.atStartOfDeclaration() {
} else if self.atStartOfDeclaration(allowInitDecl: allowInitDecl) {
return RawSyntax(self.parseDeclaration())
} else if self.atStartOfStatement() {
return RawSyntax(self.parseStatement())
} else if self.atStartOfExpression() {
return RawSyntax(self.parseExpression())
} else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowRecovery: true) {
} else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: true) {
return RawSyntax(self.parseDeclaration())
} else if self.atStartOfStatement(allowRecovery: true) {
return RawSyntax(self.parseStatement())
Expand Down
36 changes: 8 additions & 28 deletions Tests/SwiftParserTest/translated/InitDeinitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,15 +333,9 @@ final class InitDeinitTests: XCTestCase {
AssertParse(
"""
class Aaron {
init(x: Int) {}
convenience init() { init(x: 1️⃣1) }
convenience init() { init(x: 1) }
}
""",
diagnostics: [
// TODO: Old parser expected error on line 3: missing 'self.' at initializer invocation, Fix-It replacements: 24 - 24 = 'self.'
DiagnosticSpec(message: "expected type in parameter"),
DiagnosticSpec(message: "unexpected code '1' in parameter clause"),
]
"""
)
}

Expand All @@ -350,15 +344,10 @@ final class InitDeinitTests: XCTestCase {
"""
class Theodosia: Aaron {
init() {
init(x: 1️⃣2)
init(x: 2)
}
}
""",
diagnostics: [
// TODO: Old parser expected error on line 3: missing 'super.' at initializer invocation, Fix-It replacements: 5 - 5 = 'super.'
DiagnosticSpec(message: "expected type in parameter"),
DiagnosticSpec(message: "unexpected code '2' in parameter clause"),
]
"""
)
}

Expand All @@ -367,14 +356,9 @@ final class InitDeinitTests: XCTestCase {
"""
struct AaronStruct {
init(x: Int) {}
init() { init(x: 1️⃣1) }
init() { init(x: 1) }
}
""",
diagnostics: [
// TODO: Old parser expected error on line 3: missing 'self.' at initializer invocation, Fix-It replacements: 12 - 12 = 'self.'
DiagnosticSpec(message: "expected type in parameter"),
DiagnosticSpec(message: "unexpected code '1' in parameter clause"),
]
"""
)
}

Expand All @@ -383,13 +367,9 @@ final class InitDeinitTests: XCTestCase {
"""
enum AaronEnum: Int {
case A = 1
init(x: Int) { init(rawValue: x)1️⃣! }
init(x: Int) { init(rawValue: x)! }
}
""",
diagnostics: [
// TODO: Old parser expected error on line 3: missing 'self.' at initializer invocation, Fix-It replacements: 18 - 18 = 'self.'
DiagnosticSpec(message: "unexpected code '!' in initializer"),
]
"""
)
}

Expand Down