@@ -900,6 +900,112 @@ extension Parser {
900900 }
901901}
902902
903+ extension TokenConsumer {
904+ /// Disambiguate the word at the cursor looks like a keyword-prefixed syntax.
905+ ///
906+ /// - Parameters:
907+ /// - exprFlavor: The expression context. When using this function for a statement, e.g. 'yield',
908+ /// use `.basic`.
909+ /// - acceptClosure: When the next token is '{' and it looks like a closure, use this value as the result.
910+ /// - preferPostfixExpr: When the next token is '.', '(', or '[' and there is a space between the word,
911+ /// use `!preferPostfixExpr` as the result.
912+ mutating func atContextualKeywordPrefixedSyntax( exprFlavor: Parser . ExprFlavor , acceptClosure: Bool = false , preferPostfixExpr: Bool = true ) -> Bool {
913+ let next = peek ( )
914+
915+ // The next token must be at the same line.
916+ if next. isAtStartOfLine {
917+ return false
918+ }
919+
920+ switch next. rawTokenKind {
921+
922+ case . identifier, . dollarIdentifier, . wildcard:
923+ // E.g. <word> foo
924+ return true
925+
926+ case . integerLiteral, . floatLiteral,
927+ . stringQuote, . multilineStringQuote, . singleQuote, . rawStringPoundDelimiter,
928+ . regexSlash, . regexPoundDelimiter:
929+ // E.g. <word> 1
930+ return true
931+
932+ case . prefixAmpersand, . prefixOperator, . atSign, . backslash, . pound:
933+ // E.g. <word> !<expr>
934+ return true
935+
936+ case . keyword:
937+ switch Keyword ( next. tokenText) {
938+ case . as, . is, . in:
939+ // E.g. <word> is <expr>
940+ return false
941+ default :
942+ // Other lexer-classified keywords are identifier-like.
943+ // E.g. <word> self
944+ return true
945+ }
946+
947+ case . binaryOperator, . equal, . arrow, . infixQuestionMark:
948+ // E.g. <word> != <expr>
949+ return false
950+ case . postfixOperator, . postfixQuestionMark, . exclamationMark, . ellipsis:
951+ // E.g. <word>++
952+ return false
953+ case . rightBrace, . rightParen, . rightSquare:
954+ // E.g. <word>]
955+ return false
956+ case . colon, . comma:
957+ // E.g. <word>,
958+ return false
959+ case . semicolon, . endOfFile, . poundElse, . poundElseif, . poundEndif:
960+ return false
961+
962+ case . leftAngle, . rightAngle:
963+ // Lexer never produce these token kinds.
964+ return false
965+
966+ case . stringSegment, . regexLiteralPattern:
967+ // Calling this function inside a string/regex literal?
968+ return false
969+
970+ case . backtick, . poundAvailable, . poundUnavailable,
971+ . poundSourceLocation, . poundIf, . shebang, . unknown:
972+ // These are invalid for both cases
973+ // E.g. <word> #available
974+ return false
975+
976+ case . period, . leftParen, . leftSquare:
977+ // These are truly ambiguous. They can be both start of postfix expression
978+ // suffix or start of primary expression:
979+ //
980+ // - Member access vs. implicit member expression
981+ // - Call vs. tuple expression
982+ // - Subscript vs. collection literal
983+ //
984+ let hasSpace = ( next. leadingTriviaByteLength + currentToken. trailingTriviaByteLength) != 0
985+ if !hasSpace {
986+ // No space, the word is an decl-ref expression
987+ return false
988+ }
989+ return !preferPostfixExpr
990+
991+ case . leftBrace:
992+ // E.g. <word> { ... }
993+ // Trailing closure is also ambiguous:
994+ //
995+ // - Trailing closure vs. immediately-invoked closure
996+ //
997+ // Existence of whitespace can help this because people usually put a space
998+ // before trailing closures. Although it is source breaking but we prefer
999+ // parsing it as a keyword.
1000+ let isClosure = self . withLookahead {
1001+ $0. consumeAnyToken ( )
1002+ return $0. atValidTrailingClosure ( flavor: exprFlavor)
1003+ }
1004+ return isClosure && acceptClosure
1005+ }
1006+ }
1007+ }
1008+
9031009// MARK: Lookahead
9041010
9051011extension Parser . Lookahead {
@@ -949,45 +1055,10 @@ extension Parser.Lookahead {
9491055 // FIXME: 'repeat' followed by '{' could be a pack expansion
9501056 // with a closure pattern.
9511057 return self . peek ( ) . rawTokenKind == . leftBrace
952- case . yield? :
953- switch self . peek ( ) . rawTokenKind {
954- case . prefixAmpersand:
955- // "yield &" always denotes a yield statement.
956- return true
957- case . leftParen:
958- // "yield (", by contrast, must be disambiguated with additional
959- // context. We always consider it an apply expression of a function
960- // called `yield` for the purposes of the parse.
961- return false
962- case . binaryOperator:
963- // 'yield &= x' treats yield as an identifier.
964- return false
965- default :
966- // "yield" followed immediately by any other token is likely a
967- // yield statement of some singular expression.
968- return !self . peek ( ) . isAtStartOfLine
969- }
970- case . discard? :
971- let next = peek ( )
972- // The thing to be discarded must be on the same line as `discard`.
973- if next. isAtStartOfLine {
974- return false
975- }
976- switch next. rawTokenKind {
977- case . identifier, . keyword:
978- // Since some identifiers like "self" are classified as keywords,
979- // we want to recognize those too, to handle "discard self". We also
980- // accept any identifier since we want to emit a nice error message
981- // later on during type checking.
982- return true
983- default :
984- // any other token following "discard" means it's not the statement.
985- // For example, could be the function call "discard()".
986- return false
987- }
988-
989- case . then:
990- return atStartOfThenStatement ( preferExpr: preferExpr)
1058+ case . yield? , . discard? :
1059+ return atContextualKeywordPrefixedSyntax ( exprFlavor: . basic, preferPostfixExpr: true )
1060+ case . then? :
1061+ return atContextualKeywordPrefixedSyntax ( exprFlavor: . basic, preferPostfixExpr: false )
9911062
9921063 case nil :
9931064 return false
0 commit comments