Skip to content

[NFC-ish] Rework compatibility layer generation #2888

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
merged 4 commits into from
Nov 5, 2024

Conversation

beccadax
Copy link
Contributor

@beccadax beccadax commented Oct 31, 2024

This PR replaces the deprecatedName property on Child with a new, more sophisticated mechanism which supports:

• Multiple past changes (for instance, a property that has been renamed more than once) with a different initializer for each past state.
• The ability to represent the order of multiple changes and group together simultaneous changes.
• Multiple kinds of changes, including an extracted change where a group of adjacent nodes are moved into a newly-created child node.

This new childHistory mechanism also works on Traits, where it generates compatibility properties as one would expect. (We previously silently ignored deprecatedName on a child of a trait.)

This PR does not add or remove any SwiftSyntax APIs, but it does cause small, functionally equivalent changes to the generated code.

Extracted from #2882.

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

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

Looks like a great clean-up to the somewhat ad-hoc deprecation design we had before.

@@ -40,7 +40,8 @@ public enum ChildKind {
/// The child always contains a node of the given `kind`.
case node(kind: SyntaxNodeKind)
/// The child always contains a node that matches one of the `choices`.
case nodeChoices(choices: [Child])
case nodeChoices(choices: [Child], childHistory: Child.History = [])
// FIXME: We don't appear to have ever generated compatibility layers for children of node choices!
Copy link
Member

Choose a reason for hiding this comment

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

I think you resolved this comment, right?

Copy link
Contributor Author

@beccadax beccadax Oct 31, 2024

Choose a reason for hiding this comment

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

nodeChoices still doesn't have compatibility layers. I think largely because Swift can't alias enum cases—you can use a deprecated static method to construct the replacement case, but not to pattern-match it.

Copy link
Member

Choose a reason for hiding this comment

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

Instead of having a FIXME for it (which is very easy to miss), could we raise a fatalError if you try to add a compatibility layer of node choices? That way you can’t miss it if you try to add one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, there actually are existing cases where people have specified a deprecatedName: for a child of a ChildKind.nodeChoices; this information was simply ignored until now. Rather than deleting it, I thought it'd be better to preserve the info in the node definition files, but if we'd rather just delete it (which—again—would be an NFC change), I can remove the childHistory value here entirely.

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 haven't done anything with this yet, but I think I've addressed everything else you suggested.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for checking. Could you file an issue for the FIXME? Then we can take a look at it after this PR is merged.

@beccadax beccadax force-pushed the history-is-childs-play branch from a461ff2 to 75f6c3f Compare November 2, 2024 02:50
Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

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

Thanks for addressing my comments, @beccadax.

public var deprecatedMembersByTrait: [String: DeprecatedMemberInfo] = [:]

/// Cache for `replacementChild(for:)`. Ensures that we don't create
/// two different replacement nodes even if we refactor the same child twice.
Copy link
Member

Choose a reason for hiding this comment

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

I would just add

Suggested change
/// two different replacement nodes even if we refactor the same child twice.
/// two different replacement nodes even if we refactor the same child twice.
/// This is important because we use pointer equality of `Child` to decide if two children are the same.

}

internal init(nodes: [Node], traits: [Trait]) {
// Note that we compute these up-front, greedily, so that all of the mutation is isolated by the lazy var's state.
Copy link
Member

Choose a reason for hiding this comment

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

Which lazy var? Is this a leftover from a previous version?

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 was referring to the SYNTAX_COMPATIBILITY_LAYER global, which like all Swift globals, is implicitly lazily initialized with synchronization.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would this be a better way to describe the situation?

Suggested change
// Note that we compute these up-front, greedily, so that all of the mutation is isolated by the lazy var's state.
// This instance will be stored in a global that's used from multiple threads simultaneously, so it won't be safe
// to mutate once the initializer returns. We therefore do all the work to populate its tables up front, rather
// than computing it lazily on demand.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds great to me. Thanks!

@beccadax beccadax force-pushed the history-is-childs-play branch from 75f6c3f to e458bdf Compare November 4, 2024 19:23
@beccadax
Copy link
Contributor Author

beccadax commented Nov 4, 2024

@swift-ci please test

@beccadax beccadax force-pushed the history-is-childs-play branch from e458bdf to e5bde56 Compare November 4, 2024 23:02
@beccadax
Copy link
Contributor Author

beccadax commented Nov 4, 2024

SyntaxSupport/Child.swift:345: Fatal error: unexpected node has no siblings?

Turned out this was happening with base nodes, since they don't have any children (but their unexpected nodes are ignored anyway). Pulled in a fix that avoids trying to create unexpected children for base nodes.

@beccadax
Copy link
Contributor Author

beccadax commented Nov 4, 2024

@swift-ci please test

Introduce and factor out a type that represents a list of children for which an initializer ought to exist.

This changes some `@available(renamed:)` values, but nothing that should change diagnostics or behavior.
A new CompatibilityLayer type computes information about deprecated vars and inits, presenting it in a new way: by creating additional `Child` objects with the deprecated information, plus sufficient info to map it back to the current name.

This causes minor non-functional changes to the bodies of compatibility builder inits, but otherwise has no real effect in this commit. However, this new setup with separate `Child` nodes for deprecated children and a global table of information about them prepares us for further changes.
Introduce a much more sophisticated way of describing past revisions to a node’s children. This allows:

• Multiple past changes (for instance, a property that has been renamed more than once).
• The ability to represent the order of multiple changes and group together simultaneous changes.
• Multiple kinds of changes, including changes that introduce more than one member.

Note that this commit does not use any of these new capabilities; it just ports existing behavior to the new representation.
It’s always been possible to specify deprecated children, but nothing was actually done with the info until now.

Turns some manaully-generated decls into automatically-generated ones, but doesn’t change anything user-facing.
@beccadax beccadax force-pushed the history-is-childs-play branch from e5bde56 to f1fcb10 Compare November 5, 2024 02:28
@beccadax
Copy link
Contributor Author

beccadax commented Nov 5, 2024

Needed to placate swift-format.

@beccadax
Copy link
Contributor Author

beccadax commented Nov 5, 2024

@swift-ci please smoke test

@beccadax beccadax enabled auto-merge November 5, 2024 02:30
@beccadax
Copy link
Contributor Author

beccadax commented Nov 5, 2024

@swift-ci please test

@beccadax beccadax merged commit 0386d87 into swiftlang:main Nov 5, 2024
3 checks passed
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