Skip to content
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

[SwiftParser] Support function body and type member skipping #2316

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2270,4 +2270,21 @@ public let DECL_NODES: [Node] = [
),
]
),

Node(
kind: .skippedDecl,
base: .decl,
nameForDiagnostics: "skipped body",
documentation: """
Represent skipped portion of the source.
""",
traits: [],
children: [
Child(
name: "text",
kind: .token(choices: [.token(.unknown)]),
nameForDiagnostics: "text"
),
]
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ public enum SyntaxNodeKind: String, CaseIterable {
case stmt
case simpleStringLiteralExpr
case simpleStringLiteralSegmentList
case skippedDecl
case stringLiteralExpr
case stringLiteralSegmentList
case stringSegment
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftParser/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ add_swift_syntax_library(SwiftParser
CharacterInfo.swift
CollectionNodes+Parsable.swift
Declarations.swift
DelayedParsing.swift
Directives.swift
Expressions.swift
IncrementalParseTransition.swift
Expand Down
18 changes: 15 additions & 3 deletions Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ extension Parser {
} else {
whereClause = nil
}
let memberBlock = self.parseMemberBlock(introducer: extensionKeyword)
let memberBlock = self.parseMemberBlock(introducer: extensionKeyword, allowSkip: true)
return RawExtensionDeclSyntax(
attributes: attrs.attributes,
modifiers: attrs.modifiers,
Expand Down Expand Up @@ -777,9 +777,21 @@ extension Parser {
/// `introducer` is the `struct`, `class`, ... keyword that is the cause that the member decl 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 parseMemberBlock(introducer: RawTokenSyntax? = nil) -> RawMemberBlockSyntax {
mutating func parseMemberBlock(introducer: RawTokenSyntax? = nil, allowSkip: Bool = false) -> RawMemberBlockSyntax {
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
let members = parseMemberDeclList()

let members: RawMemberBlockItemListSyntax
if allowSkip,
self.options.contains(.bodySkipping),
let skipped = self.skippedBraceBody(
unless: [.hasNestedClassDeclarations, .hasOperatorDeclarations, .hasPoundDirective]
) {
let member = RawMemberBlockItemSyntax(decl: .init(skipped), semicolon: nil, arena: self.arena)
members = RawMemberBlockItemListSyntax(elements: [member], arena: self.arena)
} else {
members = parseMemberDeclList()
}

let (unexpectedBeforeRBrace, rbrace) = self.expectRightBrace(leftBrace: lbrace, introducer: introducer)

return RawMemberBlockSyntax(
Expand Down
116 changes: 116 additions & 0 deletions Sources/SwiftParser/DelayedParsing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

@_spi(RawSyntax) import SwiftSyntax

extension Parser {
mutating func skippedBraceBody(unless: Lookahead.SkipBodyResult) -> RawSkippedDeclSyntax? {
return self.withLookahead { lookahead in
let result = lookahead.advanceUntilMatchingRightBrace()
guard result.isDisjoint(with: unless) else {
Copy link
Member Author

Choose a reason for hiding this comment

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

Instead of checking the result on the skipped area, the skipping function should bail out and return nil as soon as it finds any unless condition.

return nil
}

let wholeText = SyntaxText(
baseAddress: self.currentToken.start,
count: self.currentToken.start.distance(to: lookahead.currentToken.start)
)

// Advance the Lexer to skipped position.
self.currentToken = lookahead.currentToken
self.lexemes = lookahead.lexemes

// Represent the skipped range with a single .unknown token.
let tok = RawTokenSyntax(
kind: .unknown,
wholeText: wholeText,
textRange: wholeText.startIndex..<wholeText.endIndex,
presence: .present,
tokenDiagnostic: nil,
arena: self.arena
)
return RawSkippedDeclSyntax(text: tok, arena: self.arena)
}
}
}

extension Parser.Lookahead {
struct SkipBodyResult: OptionSet {
let rawValue: UInt8

static let hasPoundDirective = Self(rawValue: 1 << 0)
static let hasOperatorDeclarations = Self(rawValue: 1 << 1)
static let hasNestedClassDeclarations = Self(rawValue: 1 << 2)
static let hasNestedTypeDeclarations = Self(rawValue: 1 << 3)
static let hasPotentialRegexLiteral = Self(rawValue: 1 << 4)
}

mutating func advanceUntilMatchingRightBrace() -> SkipBodyResult {
var openBraces = 0

var lastTokenWasFunc = false

var result = SkipBodyResult()

while self.currentToken.rawTokenKind != .endOfFile {
let token = self.currentToken
if !result.contains(.hasPoundDirective) && (
token.rawTokenKind == .poundIf ||
token.rawTokenKind == .poundElseif ||
token.rawTokenKind == .poundElse ||
token.rawTokenKind == .poundEndif ||
token.rawTokenKind == .poundSourceLocation
) {
result.insert(.hasPoundDirective)
}

if lastTokenWasFunc && !result.contains(.hasOperatorDeclarations) && (
token.rawTokenKind == .binaryOperator ||
token.rawTokenKind == .prefixOperator ||
token.rawTokenKind == .postfixOperator
) {
result.insert(.hasOperatorDeclarations)
}
lastTokenWasFunc = token.rawTokenKind == .keyword && token.tokenText == "func"

if !result.contains(.hasNestedClassDeclarations) && (
token.rawTokenKind == .keyword &&
token.tokenText == "class"
) {
result.insert(.hasNestedClassDeclarations)
}

if !result.contains(.hasNestedClassDeclarations) && (
token.rawTokenKind == .keyword && (
token.tokenText == "class" ||
token.tokenText == "enum" ||
token.tokenText == "struct" ||
token.tokenText == "actor"
)
) {
result.insert(.hasNestedTypeDeclarations)
}

if token.rawTokenKind == .leftBrace {
openBraces += 1
} else if token.rawTokenKind == .rightBrace {
if openBraces == 0 {
break
} else {
openBraces -= 1
}
}
self.consumeAnyToken()
}
return result
}
}
6 changes: 2 additions & 4 deletions Sources/SwiftParser/Lexer/Cursor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1236,10 +1236,8 @@ extension Lexer.Cursor {

fallthrough
default:
if let peekedScalar = start.peekScalar(), peekedScalar.isValidIdentifierStartCodePoint {
break
}
if let peekedScalar = start.peekScalar(), peekedScalar.isOperatorStartCodePoint {
if let peekedScalar = start.peekScalar(),
peekedScalar.isValidIdentifierStartCodePoint || peekedScalar.isOperatorStartCodePoint {
Copy link
Member Author

Choose a reason for hiding this comment

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

unrelated?

break
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Nominals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ extension Parser {
whereClause = nil
}

let memberBlock = self.parseMemberBlock(introducer: introducerKeyword)
let memberBlock = self.parseMemberBlock(introducer: introducerKeyword, allowSkip: true)
return T.init(
attributes: attrs.attributes,
modifiers: attrs.modifiers,
Expand Down
15 changes: 9 additions & 6 deletions Sources/SwiftParser/ParseSourceFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,32 @@ extension Parser {
/// Parse the source code in the given string as Swift source file. See
/// `Parser.init` for more details.
public static func parse(
source: String
source: String,
options: ParsingOptions = []
) -> SourceFileSyntax {
var parser = Parser(source)
var parser = Parser(source, options: options)
return SourceFileSyntax.parse(from: &parser)
}

/// A compiler interface that allows the enabling of experimental features.
@_spi(ExperimentalLanguageFeatures)
public static func parse(
source: UnsafeBufferPointer<UInt8>,
experimentalFeatures: ExperimentalFeatures
experimentalFeatures: ExperimentalFeatures,
options: ParsingOptions = []
) -> SourceFileSyntax {
var parser = Parser(source, experimentalFeatures: experimentalFeatures)
var parser = Parser(source, experimentalFeatures: experimentalFeatures, options: options)
return SourceFileSyntax.parse(from: &parser)
}

/// Parse the source code in the given buffer as Swift source file. See
/// `Parser.init` for more details.
public static func parse(
source: UnsafeBufferPointer<UInt8>,
maximumNestingLevel: Int? = nil
maximumNestingLevel: Int? = nil,
options: ParsingOptions = []
) -> SourceFileSyntax {
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel)
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel, options: options)
return SourceFileSyntax.parse(from: &parser)
}

Expand Down
47 changes: 35 additions & 12 deletions Sources/SwiftParser/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@
/// tokens as needed to disambiguate a parse. However, because lookahead
/// operates on a copy of the lexical stream, no input tokens are lost..
public struct Parser {
public struct ParsingOptions: OptionSet {
public let rawValue: UInt8
public init(rawValue: UInt8) {
self.rawValue = rawValue
}

public static let bodySkipping = Self(rawValue: 1 << 0)
}

var options: ParsingOptions

var arena: ParsingSyntaxArena

/// A view of the sequence of lexemes in the input.
Expand Down Expand Up @@ -168,7 +179,7 @@ public struct Parser {
return _emptyRawAttributeListSyntax!
}

/// The delegated initializer for the parser.
/// The designated initializer for the parser.
///
/// - Parameters
/// - input: An input buffer containing Swift source text. If a non-`nil`
Expand All @@ -193,8 +204,10 @@ public struct Parser {
maximumNestingLevel: Int?,
parseTransition: IncrementalParseTransition?,
arena: ParsingSyntaxArena?,
experimentalFeatures: ExperimentalFeatures
experimentalFeatures: ExperimentalFeatures,
options: ParsingOptions
) {
self.options = options
var input = input
if let arena {
self.arena = arena
Expand Down Expand Up @@ -224,7 +237,8 @@ public struct Parser {
string input: String,
maximumNestingLevel: Int?,
parseTransition: IncrementalParseTransition?,
experimentalFeatures: ExperimentalFeatures
experimentalFeatures: ExperimentalFeatures,
options: ParsingOptions
) {
var input = input
input.makeContiguousUTF8()
Expand All @@ -234,7 +248,8 @@ public struct Parser {
maximumNestingLevel: maximumNestingLevel,
parseTransition: parseTransition,
arena: nil,
experimentalFeatures: experimentalFeatures
experimentalFeatures: experimentalFeatures,
options: options
)
}
}
Expand All @@ -243,14 +258,16 @@ public struct Parser {
public init(
_ input: String,
maximumNestingLevel: Int? = nil,
parseTransition: IncrementalParseTransition? = nil
parseTransition: IncrementalParseTransition? = nil,
options: ParsingOptions = []
) {
// Chain to the private String initializer.
self.init(
string: input,
maximumNestingLevel: maximumNestingLevel,
parseTransition: parseTransition,
experimentalFeatures: []
experimentalFeatures: [],
options: options
)
}

Expand All @@ -277,15 +294,17 @@ public struct Parser {
_ input: UnsafeBufferPointer<UInt8>,
maximumNestingLevel: Int? = nil,
parseTransition: IncrementalParseTransition? = nil,
arena: ParsingSyntaxArena? = nil
arena: ParsingSyntaxArena? = nil,
options: ParsingOptions = []
) {
// Chain to the private buffer initializer.
self.init(
buffer: input,
maximumNestingLevel: maximumNestingLevel,
parseTransition: parseTransition,
arena: arena,
experimentalFeatures: []
experimentalFeatures: [],
options: options
)
}

Expand All @@ -296,14 +315,16 @@ public struct Parser {
_ input: String,
maximumNestingLevel: Int? = nil,
parseTransition: IncrementalParseTransition? = nil,
experimentalFeatures: ExperimentalFeatures
experimentalFeatures: ExperimentalFeatures,
options: ParsingOptions = []
) {
// Chain to the private String initializer.
self.init(
string: input,
maximumNestingLevel: maximumNestingLevel,
parseTransition: parseTransition,
experimentalFeatures: experimentalFeatures
experimentalFeatures: experimentalFeatures,
options: options
)
}

Expand All @@ -315,15 +336,17 @@ public struct Parser {
maximumNestingLevel: Int? = nil,
parseTransition: IncrementalParseTransition? = nil,
arena: ParsingSyntaxArena? = nil,
experimentalFeatures: ExperimentalFeatures
experimentalFeatures: ExperimentalFeatures,
options: ParsingOptions = []
) {
// Chain to the private buffer initializer.
self.init(
buffer: input,
maximumNestingLevel: maximumNestingLevel,
parseTransition: parseTransition,
arena: arena,
experimentalFeatures: experimentalFeatures
experimentalFeatures: experimentalFeatures,
options: options
)
}

Expand Down