Skip to content

clone path hierarchy nodes when their parent has a language-specific counterpart #1203

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

QuietMisdreavus
Copy link
Contributor

Bug/issue #, if applicable: rdar://144862231

Summary

When C unions are imported into Swift, they are converted into structs with computed properties to preserve union semantics. That means that the same symbol has different symbol kinds depending on the source language. This causes problems for the path hierarchy when associating those fields/properties with their parent, since there are now two separate hierarchy nodes that they could be associated with. This causes Swift-DocC to trigger a number of assertions due to the inconsistent state. (In release builds when debug assertions are compiled out, the issue is papered over with backup code, but the issue can still cause problems as evidenced with the need for workarounds like #1200.)

This PR updates the PathHierarchy initializer to clone children of these split parents to ensure that the hierarchy tree remains in a consistent state.

Dependencies

None

Testing

This is a catalog that contains Clang and Swift symbol graphs for a simple union with one field: DemoFramework.docc.zip

Steps:

  1. Build a debug build of Swift-DocC (to ensure assertions are enabled)
  2. docc convert DemoFramework.docc
  3. Ensure that conversion completes without errors.

Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

  • Added tests
  • Ran the ./bin/test script and it succeeded
  • [ n/a ] Updated documentation if necessary

@QuietMisdreavus
Copy link
Contributor Author

@swift-ci Please test

Comment on lines +167 to +180
// Make sure that the clone's children can all line up with symbols from this symbol graph.
for (childName, children) in sourceNode.children {
for child in children.storage {
guard let childSymbol = child.node.symbol else {
// We shouldn't come across any non-symbol nodes here,
// but assume they can work as child of both variants.
clonedSourceNode.add(child: child.node, kind: child.kind, hash: child.hash)
continue
}
if nodes[childSymbol.identifier.precise] === child.node {
clonedSourceNode.add(symbolChild: child.node)
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This block was added in a fit of paranoia, since i already needed to update the symbol data. I'm not sure if this is correct, but if C unions are the only thing that trigger this scenario this code shouldn't be hit in the first place. (Nested types in C create their declarations at top-level, so even declaring a type inline with a name won't create grandchild nodes of the union itself.)

@@ -558,6 +611,8 @@ extension PathHierarchy {
)
return
}

assert(child.parent == nil, "Nodes that already have a parent should not be added to a different parent.")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added this assertion to facilitate testing, but i left it in because tests seemed to pass with it present. I could take it out if you think it's a problem.

) {
self.symbol = source.symbol
self.name = source.name
self.children = children ?? source.children
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 possible to perform the deep copy recursively in this initializer instead? I believe that would be easier to follow and it would support more than one level of copying.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would still need to filter out the nodes that don't match up with the current symbol graph, which isn't accessible in this initializer. I also can't remove children after the fact since children's setter is marked private. (Unless i add a remove method, i suppose.)

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought you'd still be able to filter the list before assigning it but I haven't tried. Anyway, this is all private code so we can revisit it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would be possible to add a filtering component to the init(cloning:) initializer, but then the filtering check would be moved up into the initializer call. I did wind up tweaking the code layout to perform some of the cleanup in that call, but i feel like splitting the loop and the graph inclusion check would be Too Weird for me. Not sure. Maybe there's a better way to do it than what i'm thinking about.

@@ -148,7 +148,48 @@ struct PathHierarchy {
// would require that we redundantly create multiple nodes for the same symbol in many common cases and then merge them. To avoid doing that, we instead check
// the source symbol's path components to find the correct target symbol by matching its name.
if let targetNode = nodes[relationship.target], targetNode.name == expectedContainerName {
targetNode.add(symbolChild: sourceNode)
if sourceNode.parent == nil || sourceNode.parent === targetNode {
Copy link
Contributor

Choose a reason for hiding this comment

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

We could do nothing if the target already is the parent of the source, otherwise we might hit the added assertion.

@QuietMisdreavus
Copy link
Contributor Author

@swift-ci Please test

@QuietMisdreavus
Copy link
Contributor Author

@swift-ci Please test

@QuietMisdreavus QuietMisdreavus merged commit 38ea39d into swiftlang:main Apr 25, 2025
2 checks passed
@QuietMisdreavus QuietMisdreavus deleted the alternate-parent-variants branch April 25, 2025 15:44
QuietMisdreavus added a commit to QuietMisdreavus/swift-docc that referenced this pull request Apr 25, 2025
QuietMisdreavus added a commit that referenced this pull request Apr 29, 2025
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.

2 participants