Skip to content

Expose read-only AST Node traversal API and internal classifiers#11

Open
sewerynplazuk wants to merge 1 commit into
oozoofrog:mainfrom
sewerynplazuk:support-node-traversal
Open

Expose read-only AST Node traversal API and internal classifiers#11
sewerynplazuk wants to merge 1 commit into
oozoofrog:mainfrom
sewerynplazuk:support-node-traversal

Conversation

@sewerynplazuk

@sewerynplazuk sewerynplazuk commented May 4, 2026

Copy link
Copy Markdown

This PR enables downstream tools to traverse the demangled AST to find the nominal owners of symbols. Currently, they have to manually duplicate compiler logic to guess what constitutes a "context" or "function attribute," which may break between Swift releases.

The changes are straightforward and don't add any new logic or maintenance burden to upstream. Please let me know if this is acceptable, as it does enforce some sort of public contract.

}

var isMacroExpandion: Bool {
public var isMacroExpansion: Bool {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed typo.

@oozoofrog

oozoofrog commented May 10, 2026

Copy link
Copy Markdown
Owner

Thanks for the follow-up to #9, @sewerynplazuk — and nice catch on the isMacroExpandionisMacroExpansion typo.

One concern before merging: exposing classifiers like isContext / isFunctionAttr makes their membership part of our public contract — and that membership tracks Swift compiler changes (e.g. .IsolatedDeallocator, .DefaultOverride were added recently). Same signature, drifting behavior across Swift releases — a subtle semver promise.

What if we exposed the goal directly instead?

public extension String {
    /// The nominal type or module that owns the demangled symbol.
    var nominalOwner: SymbolOwner? { get }
}

Internally it would reuse your classifiers but keep them internal — same pattern as the existing String.symbolKind. (If even that feels too much, var nominalChain: [String]? is the strict subset.)

Two questions to size this right:

  1. Could you share a short snippet of the current call site? If nominalOwner would replace it cleanly, easy call.
  2. Just the owner, or also adjacent info (type signatures, requirements, macro expansions)? If it's more than owners, your PR's shape may actually be the right one.

The String.node introduction looks worth keeping either way. Thanks for pushing this forward!

@sewerynplazuk

Copy link
Copy Markdown
Author

Hey @oozoofrog, I'm sorry for the late reply!

Could you share a short snippet of the current call site? If nominalOwner would replace it cleanly, easy call.

That's pretty much what I have right now. The “default” part is an acceptable simplification for my use case, but I'm not sure if it's acceptable upstream. It's for sure not always correct.

    func owningNominal(of node: Node) -> Node? {
        switch node.kind {
        case .Class, .Structure, .Enum, .Protocol:
            return node
        case .Module:
            // Module is the top-level namespace with no nominal owner.
            return nil
        case .Extension:
            return node.children.indices.contains(1) ? owningNominal(of: node.children[1]) : nil
        case .Global:
            return node.children.first { !$0.kind.isFunctionAttr }.flatMap(owningNominal)
        case let kind where kind.isFunctionAttr:
            // Function attributes are compiler decorations, not ownership contexts.
            return nil
        default:
            return node.children.first.flatMap(owningNominal)
        }
    }

Just the owner, or also adjacent info (type signatures, requirements, macro expansions)? If it's more than owners, your PR's shape may actually be the right one.

Just the owner for my use case.

@oozoofrog

Copy link
Copy Markdown
Owner

Hi @sewerynplazuk,

No worries, I’m late getting back to this too. Thanks for sharing the call site. This clarifies the use case.

Since the goal is specifically to find the owner, I don’t think we should expose isContext / isFunctionAttr as public API here. Those classifiers are useful internally, but their membership follows Swift compiler mangling changes, so making them public would turn an implementation detail into a semver contract.

I think String.node is still worth keeping as a read-only traversal entry point. But for this use case, I’d prefer a narrower API such as nominalOwner or nominalChain, with the classifier logic kept internal and covered by tests.

Also, since you mentioned the current fallback is not always correct, I don’t think we should merge that behavior implicitly through public classifiers. Could you revise the PR in that direction?

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