Skip to content

Simplify checking if a token can be an argument label #1911

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 2 commits into from
Jul 23, 2023
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
23 changes: 10 additions & 13 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -589,21 +589,18 @@ extension Parser {
// First check to see if we have the start of a regex literal `/.../`.
// tryLexRegexLiteral(/*forUnappliedOperator*/ false)

switch self.currentToken {
// Try parse an 'if' or 'switch' as an expression. Note we do this here in
// parseUnaryExpression as we don't allow postfix syntax to hang off such
// expressions to avoid ambiguities such as postfix '.member', which can
// currently be parsed as a static dot member for a result builder.
case TokenSpec(.switch):
if self.at(.keyword(.switch)) {
return RawExprSyntax(
parseSwitchExpression(switchHandle: .constant(.keyword(.switch)))
)
case TokenSpec(.if):
} else if self.at(.keyword(.if)) {
return RawExprSyntax(
parseIfExpression(ifHandle: .constant(.keyword(.if)))
)
default:
break
}

switch self.at(anyIn: ExpressionPrefixOperator.self) {
Expand Down Expand Up @@ -951,7 +948,7 @@ extension Parser {
// Check if the first '#if' body starts with '.' <identifier>, and parse
// it as a "postfix ifconfig expression".
do {
var backtrack = self.lookahead()
var lookahead = self.lookahead()
// Skip to the first body. We may need to skip multiple '#if' directives
// since we support nested '#if's. e.g.
// baseExpr
Expand All @@ -960,13 +957,13 @@ extension Parser {
// .someMember
var loopProgress = LoopProgressCondition()
repeat {
backtrack.eat(.poundIf)
while !backtrack.at(.endOfFile) && !backtrack.currentToken.isAtStartOfLine {
backtrack.skipSingle()
lookahead.eat(.poundIf)
while !lookahead.at(.endOfFile) && !lookahead.currentToken.isAtStartOfLine {
lookahead.skipSingle()
}
} while backtrack.at(.poundIf) && backtrack.hasProgressed(&loopProgress)
} while lookahead.at(.poundIf) && lookahead.hasProgressed(&loopProgress)

guard backtrack.isAtStartOfPostfixExprSuffix() else {
guard lookahead.isAtStartOfPostfixExprSuffix() else {
break
}
}
Expand Down Expand Up @@ -2084,7 +2081,7 @@ extension Parser {
let unexpectedBeforeLabel: RawUnexpectedNodesSyntax?
let label: RawTokenSyntax?
let colon: RawTokenSyntax?
if currentToken.canBeArgumentLabel(allowDollarIdentifier: true) && self.peek().rawTokenKind == .colon {
if self.atArgumentLabel(allowDollarIdentifier: true) && self.peek().rawTokenKind == .colon {
(unexpectedBeforeLabel, label) = parseArgumentLabel()
colon = consumeAnyToken()
} else {
Expand Down Expand Up @@ -2169,7 +2166,7 @@ extension Parser.Lookahead {
// Fast path: the next two tokens must be a label and a colon.
// But 'default:' is ambiguous with switch cases and we disallow it
// (unless escaped) even outside of switches.
if !self.currentToken.canBeArgumentLabel()
if !self.atArgumentLabel()
|| self.at(.keyword(.default))
|| self.peek().rawTokenKind != .colon
{
Expand Down
20 changes: 9 additions & 11 deletions Sources/SwiftParser/Names.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extension Parser {
}

mutating func parseArgumentLabel() -> (RawUnexpectedNodesSyntax?, RawTokenSyntax) {
guard self.currentToken.canBeArgumentLabel(allowDollarIdentifier: true) else {
guard self.atArgumentLabel(allowDollarIdentifier: true) else {
return (nil, missingToken(.identifier))
}
if let dollarIdent = self.consume(if: .dollarIdentifier) {
Expand Down Expand Up @@ -98,7 +98,7 @@ extension Parser {
// A close parenthesis, if empty lists are allowed.
let nextIsRParen = flags.contains(.zeroArgCompoundNames) && next.rawTokenKind == .rightParen
// An argument label.
let nextIsArgLabel = next.canBeArgumentLabel() || next.rawTokenKind == .colon
let nextIsArgLabel = next.isArgumentLabel() || next.rawTokenKind == .colon

guard nextIsRParen || nextIsArgLabel else {
return nil
Expand All @@ -117,7 +117,7 @@ extension Parser {
var loopProgress = LoopProgressCondition()
while !self.at(.endOfFile, .rightParen) && self.hasProgressed(&loopProgress) {
// Check to see if there is an argument label.
precondition(self.currentToken.canBeArgumentLabel() && self.peek().rawTokenKind == .colon)
precondition(self.atArgumentLabel() && self.peek().rawTokenKind == .colon)
let name = self.consumeAnyToken()
let (unexpectedBeforeColon, colon) = self.expect(.colon)
elements.append(
Expand Down Expand Up @@ -242,7 +242,7 @@ extension Parser.Lookahead {
var loopProgress = LoopProgressCondition()
while !lookahead.at(.endOfFile, .rightParen) && lookahead.hasProgressed(&loopProgress) {
// Check to see if there is an argument label.
guard lookahead.currentToken.canBeArgumentLabel() && lookahead.peek().rawTokenKind == .colon else {
guard lookahead.atArgumentLabel() && lookahead.peek().rawTokenKind == .colon else {
return false
}

Expand All @@ -261,18 +261,16 @@ extension Parser.Lookahead {
}

extension Lexer.Lexeme {
func canBeArgumentLabel(allowDollarIdentifier: Bool = false) -> Bool {
// `inout` is reserved as an argument label for historical reasons.
if TypeSpecifier(lexeme: self) == .inout {
return false
}

switch self.rawTokenKind {
func isArgumentLabel(allowDollarIdentifier: Bool = false) -> Bool {
switch self {
case .identifier, .wildcard:
// Identifiers, escaped identifiers, and '_' can be argument labels.
return true
case .dollarIdentifier:
return allowDollarIdentifier
case .keyword(.inout):
// `inout` cannot be an argument label for historical reasons.
return false
default:
// All other keywords can be argument labels.
return self.isLexerClassifiedKeyword
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftParser/Nominals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,9 @@ extension Parser {
inheritanceClause: nil,
genericWhereClause: nil,
memberBlock: RawMemberDeclBlockSyntax(
leftBrace: RawTokenSyntax(missing: .leftBrace, arena: self.arena),
leftBrace: missingToken(.leftBrace),
members: RawMemberDeclListSyntax(elements: [], arena: self.arena),
rightBrace: RawTokenSyntax(missing: .rightBrace, arena: self.arena),
rightBrace: missingToken(.rightBrace),
arena: self.arena
),
arena: self.arena
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftParser/Parameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ extension Parser {
fileprivate mutating func parseParameterNames() -> ParameterNames {
let unexpectedBeforeFirstName: RawUnexpectedNodesSyntax?
let firstName: RawTokenSyntax?
if self.currentToken.canBeArgumentLabel(allowDollarIdentifier: true) {
if self.atArgumentLabel(allowDollarIdentifier: true) {
(unexpectedBeforeFirstName, firstName) = self.parseArgumentLabel()
} else {
(unexpectedBeforeFirstName, firstName) = (nil, nil)
}

let unexpectedBeforeSecondName: RawUnexpectedNodesSyntax?
let secondName: RawTokenSyntax?
if self.currentToken.canBeArgumentLabel(allowDollarIdentifier: true) {
if self.atArgumentLabel(allowDollarIdentifier: true) {
(unexpectedBeforeSecondName, secondName) = self.parseArgumentLabel()
} else {
(unexpectedBeforeSecondName, secondName) = (nil, nil)
Expand Down Expand Up @@ -280,7 +280,7 @@ extension Parser {
// to be an argument label, don't parse any parameters.
let shouldSkipParameterParsing =
lparen.isMissing
&& (!currentToken.canBeArgumentLabel(allowDollarIdentifier: true) || currentToken.isLexerClassifiedKeyword)
&& (!self.atArgumentLabel(allowDollarIdentifier: true) || currentToken.isLexerClassifiedKeyword)
if !shouldSkipParameterParsing {
var keepGoing = true
var loopProgress = LoopProgressCondition()
Expand Down
7 changes: 6 additions & 1 deletion Sources/SwiftParser/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ public struct Parser {
var arena: ParsingSyntaxArena
/// A view of the sequence of lexemes in the input.
var lexemes: Lexer.LexemeSequence
/// The current token. If there was no input, this token will have a kind of `.endOfFile`.
/// The current token that should be consumed next.
///
/// If the end of the source file is reached, this is `.endOfFile`.
///
/// - Important: You should almost never need to access this token directly
/// in the parser. Instead, prefer using the `at` methods.
var currentToken: Lexer.Lexeme

/// The current nesting level, i.e. the number of tokens that
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftParser/Patterns.swift
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ extension Parser.Lookahead {
}

// To have a parameter name here, we need a name.
guard self.currentToken.canBeArgumentLabel(allowDollarIdentifier: true) else {
guard self.atArgumentLabel(allowDollarIdentifier: true) else {
return false
}

Expand All @@ -397,7 +397,7 @@ extension Parser.Lookahead {
}

// If the next token can be an argument label, we might have a name.
if nextTok.canBeArgumentLabel(allowDollarIdentifier: true) {
if nextTok.isArgumentLabel(allowDollarIdentifier: true) {
// If the first name wasn't a contextual keyword, we're done.
if !self.at(.keyword(.isolated))
&& !self.at(.keyword(.some))
Expand All @@ -420,7 +420,7 @@ extension Parser.Lookahead {
return true // isolated :
}
self.consumeAnyToken()
return self.currentToken.canBeArgumentLabel(allowDollarIdentifier: true) && self.peek().rawTokenKind == .colon
return self.atArgumentLabel(allowDollarIdentifier: true) && self.peek().rawTokenKind == .colon
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ extension Parser {

// label-name → identifier
mutating func parseOptionalControlTransferTarget() -> RawTokenSyntax? {
guard !self.currentToken.isAtStartOfLine else {
guard !self.atStartOfLine else {
return nil
}

Expand Down
7 changes: 6 additions & 1 deletion Sources/SwiftParser/TokenConsumer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ extension TokenConsumer {
/// If this is the case, return the `Subset` case that the parser is positioned in
/// as well as a handle to consume that token.
@inline(__always)
mutating func at<SpecSet: TokenSpecSet>(anyIn specSet: SpecSet.Type) -> (SpecSet, TokenConsumptionHandle)? {
mutating func at<SpecSet: TokenSpecSet>(anyIn specSet: SpecSet.Type) -> (spec: SpecSet, handle: TokenConsumptionHandle)? {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.currentToken, choices: specSet.allCases.map(\.spec))
Expand Down Expand Up @@ -308,4 +308,9 @@ extension TokenConsumer {
return false
}
}

/// Whether the current token can be a function argument label.
func atArgumentLabel(allowDollarIdentifier: Bool = false) -> Bool {
return self.currentToken.isArgumentLabel(allowDollarIdentifier: allowDollarIdentifier)
}
}
2 changes: 1 addition & 1 deletion Sources/SwiftParser/TopLevel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension Parser {
/// as unexpected nodes that have the `isMaximumNestingLevelOverflow` bit set.
/// Check this in places that are likely to cause deep recursion and if this returns non-nil, abort parsing.
mutating func remainingTokensIfMaximumNestingLevelReached() -> RawUnexpectedNodesSyntax? {
if nestingLevel > self.maximumNestingLevel && self.currentToken.rawTokenKind != .endOfFile {
if nestingLevel > self.maximumNestingLevel && !self.at(.endOfFile) {
let remainingTokens = self.consumeRemainingTokens()
return RawUnexpectedNodesSyntax(elements: remainingTokens, isMaximumNestingLevelOverflow: true, arena: self.arena)
} else {
Expand Down
56 changes: 40 additions & 16 deletions Sources/SwiftParser/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,19 +235,49 @@ extension Parser {
mutating func parseSimpleType(
stopAtFirstPeriod: Bool = false
) -> RawTypeSyntax {
enum TypeBaseStart: TokenSpecSet {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So much boilerplate :(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a stupid question: is it possible to use one of the generated ones?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think there’s any one that has these cases because we do walk into different parsing functions based on the token, similar to ExpressionModifierKeyword.

case `Self`
case `Any`
case identifier
case leftParen
case leftSquare
case wildcard

init?(lexeme: Lexer.Lexeme) {
switch PrepareForKeywordMatch(lexeme) {
case .keyword(.Self): self = .Self
case .keyword(.Any): self = .Any
case .identifier: self = .identifier
case .leftParen: self = .leftParen
case .leftSquare: self = .leftSquare
case .wildcard: self = .wildcard
default: return nil
}
}

var spec: TokenSpec {
switch self {
case .Self: return .keyword(.Self)
case .Any: return .keyword(.Any)
case .identifier: return .identifier
case .leftParen: return .leftParen
case .leftSquare: return .leftSquare
case .wildcard: return .wildcard
}
}
}

var base: RawTypeSyntax
switch self.currentToken {
case TokenSpec(.Self),
TokenSpec(.Any),
TokenSpec(.identifier):
switch self.at(anyIn: TypeBaseStart.self)?.spec {
case .Self, .Any, .identifier:
base = self.parseTypeIdentifier()
case TokenSpec(.leftParen):
case .leftParen:
base = RawTypeSyntax(self.parseTupleTypeBody())
case TokenSpec(.leftSquare):
case .leftSquare:
base = RawTypeSyntax(self.parseCollectionType())
case TokenSpec(.wildcard):
case .wildcard:
base = RawTypeSyntax(self.parsePlaceholderType())
default:
case nil:
return RawTypeSyntax(RawMissingTypeSyntax(arena: self.arena))
}

Expand Down Expand Up @@ -508,7 +538,7 @@ extension Parser {
second = nil
unexpectedBeforeColon = nil
colon = parsedColon
} else if self.currentToken.canBeArgumentLabel(allowDollarIdentifier: true) && self.peek().rawTokenKind == .colon {
} else if self.atArgumentLabel(allowDollarIdentifier: true) && self.peek().rawTokenKind == .colon {
(unexpectedBeforeSecond, second) = self.parseArgumentLabel()
(unexpectedBeforeColon, colon) = self.expect(.colon)
} else {
Expand Down Expand Up @@ -802,7 +832,7 @@ extension Parser.Lookahead {
// by a type annotation.
if self.startsParameterName(isClosure: false, allowMisplacedSpecifierRecovery: false) {
self.consumeAnyToken()
if self.currentToken.canBeArgumentLabel() {
if self.atArgumentLabel() {
self.consumeAnyToken()
guard self.at(.colon) else {
return false
Expand Down Expand Up @@ -1061,12 +1091,6 @@ extension Parser {
}

extension Lexer.Lexeme {
var isAnyOperator: Bool {
return self.rawTokenKind == .binaryOperator
|| self.rawTokenKind == .postfixOperator
|| self.rawTokenKind == .prefixOperator
}

var isGenericTypeDisambiguatingToken: Bool {
switch self.rawTokenKind {
case .rightParen,
Expand Down