Skip to content

Add methods from RangeReplaceableCollection to SyntaxCollection and remove add* methods on syntax nodes that have a collection as child #1958

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

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Jul 27, 2023

Especially the + operators make manipulation of SyntaxCollection a lot easier.

SyntaxCollection doesn’t conform to RangeReplaceableCollection because of two reasons:

  • RangeReplaceableCollection assumes that creating a new colleciton and adding all elements from another collection to it results in the same collection. This is not true for SyntaxCollection because the new collection wouldn’t have a parent. This causes eg. the default implementation of filter to misbehave.
  • It can’t make the complexity guarantees that RangeReplaceableCollection requires, e.g. append is O(tree depth) instead of O(1).

For the same reason, also remove the conformance to MutableCollection.

With those operators and methods, we no longer need the add* methods on syntax nodes that have a syntax collection as their child.

@ahoppen ahoppen requested a review from bnbarham July 27, 2023 17:06
@ahoppen
Copy link
Member Author

ahoppen commented Jul 27, 2023

@swift-ci Please test

ahoppen added a commit to ahoppen/swift-format that referenced this pull request Jul 27, 2023
With swiftlang/swift-syntax#1958, `node.filter` will return a new `SyntaxCollection` that has the filtered elements removed (instead of the array it’s currently returning). Since that node has elements removed, it will have a new parent and thus all the nodes inside of it have new IDs. Use `where` after `for` to get the elements with the same IDs and just don’t iterate the elements that don’t satisfy the condition. This is also more performant because it doesn’t create an intermediate array.
@ahoppen
Copy link
Member Author

ahoppen commented Jul 27, 2023

swiftlang/swift-format#575

@swift-ci Please test

@ahoppen ahoppen force-pushed the ahoppen/syntax-collection-range-replaceable branch from 4635597 to b690ece Compare July 27, 2023 23:45
@ahoppen
Copy link
Member Author

ahoppen commented Jul 27, 2023

swiftlang/swift-format#575

@swift-ci Please test

@ahoppen
Copy link
Member Author

ahoppen commented Jul 28, 2023

swiftlang/swift-format#575

@swift-ci Please test Windows

ahoppen added a commit to ahoppen/swift-format that referenced this pull request Jul 28, 2023
With swiftlang/swift-syntax#1958, `node.filter` will return a new `SyntaxCollection` that has the filtered elements removed (instead of the array it’s currently returning). Since that node has elements removed, it will have a new parent and thus all the nodes inside of it have new IDs. Use `where` after `for` to get the elements with the same IDs and just don’t iterate the elements that don’t satisfy the condition. This is also more performant because it doesn’t create an intermediate array.
@ahoppen ahoppen force-pushed the ahoppen/syntax-collection-range-replaceable branch from b690ece to d069a9f Compare July 28, 2023 14:46
@ahoppen
Copy link
Member Author

ahoppen commented Jul 28, 2023

@swift-ci Please test

@ahoppen
Copy link
Member Author

ahoppen commented Jul 28, 2023

@swift-ci Please test Windows

Copy link
Contributor

@bnbarham bnbarham left a comment

Choose a reason for hiding this comment

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

My main concern here is that many of the methods list a Big O that isn't true for our implementation. Eg. even something like append ends up doing much more work than you'd otherwise expect (since it is constructing all parents + has to copy the elements into a new raw syntax node).

That's what all the add* are doing today though, so while I don't think this is blocking, perhaps we should add some extra documentation in. There's also the filter case, which we should probably make a custom implementation for.

@@ -33,6 +33,11 @@ extension SyntaxCollection {
self.init(Syntax(data))!
}

/// Create an empty ``SyntaCollection`` with no children.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Create an empty ``SyntaCollection`` with no children.
/// Create an empty ``SyntaxCollection`` with no children.

@ahoppen ahoppen force-pushed the ahoppen/syntax-collection-range-replaceable branch from d069a9f to fc54536 Compare July 28, 2023 21:36
@ahoppen
Copy link
Member Author

ahoppen commented Jul 28, 2023

I added an overload of filter and two more to override the methods in RangeReplacableCollection that were calling Self() and thus dropped the parent. Could you take another quick look at them @bnbarham?

@ahoppen
Copy link
Member Author

ahoppen commented Jul 28, 2023

swiftlang/swift-format#575

@swift-ci Please test

@@ -33,6 +33,11 @@ extension SyntaxCollection {
self.init(Syntax(data))!
}

/// Create an empty ``SyntaxCollection`` with no children.
public init() {
self.init([])
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this is the other downside of using RangeReplaceableCollection. This constructor should really never be used and makes it super easy to drop the parent accidentally.

@kimdv
Copy link
Contributor

kimdv commented Jul 29, 2023

swiftlang/swift-format#575

@swift-ci Please test Windows

@ahoppen ahoppen force-pushed the ahoppen/syntax-collection-range-replaceable branch from fc54536 to e07c1bf Compare August 1, 2023 23:12
@ahoppen ahoppen changed the title Make SyntaxCollection conform to RangeReplaceableCollection and remove add* methods on syntax nodes that have a collection as child Add methods from RangeReplaceableCollection to SyntaxCollection and remove add* methods on syntax nodes that have a collection as child Aug 1, 2023
@ahoppen ahoppen requested a review from bnbarham August 1, 2023 23:14
@ahoppen ahoppen force-pushed the ahoppen/syntax-collection-range-replaceable branch from e07c1bf to c827d90 Compare August 1, 2023 23:18
@ahoppen
Copy link
Member Author

ahoppen commented Aug 1, 2023

@swift-ci Please test

Comment on lines 171 to 175
let addedElements: ArrayElementListSyntax = Array([integerLiteralElement(4)]) + array.elements
XCTAssertEqual(addedElements.count, 4)
// If the first collection of + doesn’t have a parent, it doesn’t really
// make sense to inherit the parent from the second collection.
XCTAssert(addedElements.parent?.is(ArrayExprSyntax.self) ?? false)
Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't expect this. This is basically prepending and I think I'd want to keep the parent in that case?

Copy link
Contributor

Choose a reason for hiding this comment

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

testPrependingElement tests this case FWIW

Copy link
Member Author

Choose a reason for hiding this comment

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

But that’s what it’s testing. after adding the elements, the parent should still be an ArrayExprSyntax. Or am I misunderstanding you?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's... true 🤔 The comment seems to be suggesting the opposite doesn't it? And then I read the assert as an XCTAssertFalse.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, the comment was outdated…

// If the first collection of + doesn’t have a parent, it doesn’t really
// make sense to inherit the parent from the second collection.
XCTAssert(addedElements.parent?.is(ArrayExprSyntax.self) ?? false)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be good to test the added functions too (append, insert, replace). Or update the existing ones - they all manipulate the underlying list for the most part.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added tests

Especially the `+` operators make manipulation of `SyntaxCollection` a lot easier.

`SyntaxCollection` doesn’t conform to `RangeReplaceableCollection` because of two reasons:
 - `RangeReplaceableCollection` assumes that creating a new colleciton and adding all elements from another collection to it results in the same collection. This is not true for `SyntaxCollection` because the new collection wouldn’t have a parent. This causes eg. the default implementation of `filter` to misbehave.
 - It can’t make the complexity guarantees that `RangeReplaceableCollection` requires, e.g. `append` is `O(tree depth)` instead of `O(1)`.

For the same reason, also remove the conformance to `MutableCollection`.
@ahoppen ahoppen force-pushed the ahoppen/syntax-collection-range-replaceable branch from c827d90 to 744275d Compare August 2, 2023 16:38
@ahoppen
Copy link
Member Author

ahoppen commented Aug 2, 2023

@swift-ci Please test

@ahoppen
Copy link
Member Author

ahoppen commented Aug 2, 2023

@swift-ci Please test Windows

XCTAssertNotNil(newArrayElementList.child(at: 1))
XCTAssert(!newArrayElementList.child(at: 1)!.kind.isSyntaxCollection)
XCTAssertEqual("\(newArrayElementList.child(at: 1)!)", "1")
fileprivate func assertSyntaxCollectionManipulation(
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be worth adding a parent in here and then checking we still have a parent in the transformed? Or are there cases where that's not always true?

Copy link
Member Author

Choose a reason for hiding this comment

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

That’s why I am performing all the transformations on an ArrayExprSyntax instead of the actual collection type ArrayElementListSyntax. That way the substructure test checks that we still have the collection anchored in a parent.

@ahoppen ahoppen merged commit 250b431 into swiftlang:main Aug 3, 2023
@ahoppen ahoppen deleted the ahoppen/syntax-collection-range-replaceable branch August 3, 2023 00:21
allevato pushed a commit to allevato/swift-format that referenced this pull request Sep 14, 2023
With swiftlang/swift-syntax#1958, `node.filter` will return a new `SyntaxCollection` that has the filtered elements removed (instead of the array it’s currently returning). Since that node has elements removed, it will have a new parent and thus all the nodes inside of it have new IDs. Use `where` after `for` to get the elements with the same IDs and just don’t iterate the elements that don’t satisfy the condition. This is also more performant because it doesn’t create an intermediate array.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants