Skip to content

Add incremental parse function that return a new IncrementalParseResult #2299

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

Closed
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
40 changes: 39 additions & 1 deletion Sources/SwiftParser/ParseSourceFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
@_spi(RawSyntax) import SwiftSyntax

extension Parser {
public struct IncrementalParseResult {
/// The parsed tree.
public let tree: SourceFileSyntax
/// ``LookaheadRanges`` that describe how far the parser looked ahead while parsing a node,
/// which is necessary to construct an ``IncrementalParseTransition`` for a subsequent incremental parse.
public let lookaheadRanges: LookaheadRanges
}

/// Parse the source code in the given string as Swift source file. See
/// `Parser.init` for more details.
public static func parse(
Expand Down Expand Up @@ -62,12 +70,42 @@ extension Parser {
/// how far the parser looked ahead while parsing a node, which is
/// necessary to construct an ``IncrementalParseTransition`` for a
/// subsequent incremental parse
@available(*, deprecated, message: "Use the new version that returns a struct instead.")
public static func parseIncrementally(
source: String,
parseTransition: IncrementalParseTransition?
) -> (tree: SourceFileSyntax, lookaheadRanges: LookaheadRanges) {
let result: IncrementalParseResult = parseIncrementally(source: source, parseTransition: parseTransition)
return (result.tree, result.lookaheadRanges)
}

/// Parse the source code in the given string as Swift source file with support
/// for incremental parsing.
///
/// When parsing a source file for the first time, invoke `parseIncrementally`
/// with `parseTransition: nil`. This returns the initial tree as well as
/// ``LookaheadRanges``. If an edit is made to the source file, an
/// ``IncrementalParseTransition`` can be constructed from the initial tree
/// and its ``LookaheadRanges``. When invoking `parseIncrementally` again with
/// the post-edit source and that parse transition, the parser will re-use
/// nodes that haven’t changed.
///
/// - Parameters:
/// - source: The source code to parse
/// - parseTransition: If a similar source file has already been parsed, the
/// ``IncrementalParseTransition`` that contains the previous tree as well
/// as the edits that were performed to it.
/// - Returns: The ``IncrementalParseResult``.
public static func parseIncrementally(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay this will break as is, as the compiler cannot figure out what function to use.

We could, for now, say that it should use @_disfavoredOverload on the new one if we want.

Then people can transition

source: String,
parseTransition: IncrementalParseTransition?
) -> IncrementalParseResult {
var parser = Parser(source, parseTransition: parseTransition)
return (SourceFileSyntax.parse(from: &parser), parser.lookaheadRanges)

return IncrementalParseResult(
tree: SourceFileSyntax.parse(from: &parser),
lookaheadRanges: parser.lookaheadRanges
)
}

/// Parse the source code in the given buffer as Swift source file with support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,35 +44,35 @@ public func assertIncrementalParse(
let originalString = String(originalSource)
let editedString = String(editedSource)

let (originalTree, lookaheadRanges) = Parser.parseIncrementally(source: originalString, parseTransition: nil)
let originalResult: Parser.IncrementalParseResult = Parser.parseIncrementally(source: originalString, parseTransition: nil)

var reusedNodes: [Syntax] = []
let transition = IncrementalParseTransition(
previousTree: originalTree,
previousTree: originalResult.tree,
edits: concurrentEdits,
lookaheadRanges: lookaheadRanges,
lookaheadRanges: originalResult.lookaheadRanges,
reusedNodeCallback: { reusedNodes.append($0) }
)

let newTree = Parser.parse(source: editedString)
let (incrementallyParsedNewTree, _) = Parser.parseIncrementally(source: editedString, parseTransition: transition)
let incrementalParseResult: Parser.IncrementalParseResult = Parser.parseIncrementally(source: editedString, parseTransition: transition)

// Round-trip
assertStringsEqualWithDiff(
editedString,
"\(incrementallyParsedNewTree)",
"\(incrementalParseResult.tree)",
additionalInfo: """
Source failed to round-trip when parsing incrementally

Actual syntax tree:
\(incrementallyParsedNewTree.debugDescription)
\(incrementalParseResult.tree.debugDescription)
""",
file: file,
line: line
)

// Substructure
let subtreeMatcher = SubtreeMatcher(incrementallyParsedNewTree, markers: [:])
let subtreeMatcher = SubtreeMatcher(incrementalParseResult.tree, markers: [:])
do {
try subtreeMatcher.assertSameStructure(newTree, includeTrivia: true, file: file, line: line)
} catch {
Expand Down