-
Notifications
You must be signed in to change notification settings - Fork 425
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
SwiftSyntax support for experimental @abi attribute #2882
base: main
Are you sure you want to change the base?
Conversation
@swift-ci please test |
I really dislike using implicitly unwrapped optionals here. It’s just waiting to haunt us later when we forget to check for This leaves us with one more option: Add new syntax nodes for all the type declarations. Maybe it would be OK if they are completely separate from the existing type nodes but just shared the parsing logic. I’ll need to think about that some more. |
Is there a way that we could use a non-optional |
Another option I considered was adding a new |
I guess this would look something like extracting
Then you could put compatibility properties on This is a rather large refactor, but I think it could work. As a bonus, |
de2dc9f
to
f7dfc81
Compare
This has been changed from the original implementation. The new version extracts a new "header" child node from nodes like Generating the new decls I wanted—particularly their compatibility layers—took substantial refactoring of CodeGenerator, including the introduction of a new |
@ahoppen I'd love your opinion on this modification. |
@swift-ci please test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two preliminary comments from an initial look at the changes. I think we’re on the right track but I don’t think it makes sense to give this PR a full review until they are addressed.
@@ -180,13 +180,98 @@ public let COMMON_NODES: [Node] = [ | |||
parserFunction: "parseDeclaration" | |||
), | |||
|
|||
Node( | |||
kind: .declGroupHeader, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of the other base nodes (DeclSyntax
, ExprSyntax
etc.) have any children and I think we will get crashes if any of the nodes whose base is .declGroupHeader
are layout-incompatible with declGroupHeader
. DeclGroupHeaderSyntax
will assume that the first child in the node is attributes
but that might not be the case for a node inheriting from .declGroupHeader
.
That said, I would make the base of all header nodes .syntax
and create a trait for the decl group headers instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little reluctant to do this because I get a fair bit of mileage out of having DeclGroupHeaderSyntax
be a concrete type instead of an existential. For instance, both ABIAttributeSyntax.Provider
and the modified version of HasTrailingMemberBlock
use the concrete DeclGroupHeaderSyntax
; the former could not easily use an existential because there's no way to constrain a child node to have a trait, and the latter because a concrete type is needed to provide a contextual type for the string literal.
(If it helps, the DeclGroupHeaderSyntax
accessors don't directly access the children—they indirect through the existential so they use the leaf type's layout.)
If you can suggest another design that would still give me a concrete node which could contain any of the headers, I'd be open to that change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me think about it some more. It might also become clearer to me once #2888 is merged and this PR is rebased so it doesn’t contain those changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @ahoppen , we now have the other PR merged. Do you have more specific suggestions in mind?
This is accurate for all NamedDeclSyntax conformers so far, so I feel comfortable doing it. This also improves the accuracy of the “conforms if it can” validation by also checking the trait’s base kind against the node. That eliminates several false positives in the test.
This is the base node for the headers we are about to extract. Unlike other base nodes, DeclGroupHeaderSyntax has children, which are treated as protocol requirements for derived nodes.
It actually ought to be the union of three token kind subsets; turn it into a custom type so we can do that.
This also involves changing Child.Refactoring to support a new kind of change: “extracted”, in which a group of adjacent children are moved into a new child node. Future commits will introduce clients that use the new *DeclHeaderSyntax nodes directly.
Clears up a bunch of warnings.
`DeclGroup` now requires an initializer that can build a group from a header and member block. With that in place, we can reimplement `HasTrailingMemberDeclBlock` to avoid reparsing anything. Which is kind of neat.
Matches work in swiftlang/swift#76878.
f7dfc81
to
54ce286
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments inline.
Overall, I’m still skeptical of the new DeclGroupHeaderSyntax
header base type because:
- The reasons from SwiftSyntax support for experimental @abi attribute #2882 (comment)
- Accessing any properties of it requires an existential conversion, which may be unexpected performance-wise.
I would prefer if ABIAttributeSyntax.Provider
listed all the headers nodes it supports, just like it already lists types like typealiasDecl
and funcDecl
.
@@ -267,6 +272,31 @@ public let ATTRIBUTE_NODES: [Node] = [ | |||
] | |||
), | |||
|
|||
Node( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add an entry to the release notes for the new/updated nodes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, the node is hidden behind an experimental feature. Should I still document it now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I probably didn’t put the comment at the right place. I meant adding a release note entry for the nodes that change and that are not SPI, eg. the introduction of ClassDeclSyntax.header
.
} | ||
|
||
@available(*, deprecated, renamed: "actorHeader.name") | ||
public var name: TokenSyntax { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m a little hesitant to deprecate these accessors and am debating whether we should keep them as non-deprecated convenience accessors. Some thoughts on why:
- Using
classDeclSyntax.classHeader.name
feels more complicated than justclassDeclSyntax.name
if you just want to get a class’s name, which shouldn’t be a complicated operation.- But then, some other parts of the syntax tree are a little convoluted to access already (eg.
GenericArgumentListSyntax
containsGenericArgumentSyntax
, which containsTypeSyntax
and a trailing comma), so not sure if this is really a super strong argument.
- But then, some other parts of the syntax tree are a little convoluted to access already (eg.
- I expect quite a few codebases to use these properties, so I expect the to incur deprecation warnings in quite a few codebases and they can’t fully switch over to
classHeader.name
if they need to support older swift-syntax versions as well. - The conformance of
ClassDeclSyntax
toNamedDeclHeader
,WithAttributesSyntax
,WithGenericParameters
, andWithModifiers
relies on these accessors. If we deprecate these accessors, we should also deprecate the conformance ofClassDeclSyntax
to these traits. But there’s no way to deprecate protocol conformances and I assume that these are used somewhat wildly, so I’m not sure how to do this transition.
@rintaro I would appreciate your opinion here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do understand your concerns here; I separated out the deprecation changes into their own commit specifically so you could evaluate this impact.
I could add something in CodeGenerator to allow certain compatibility layer APIs to be non-deprecated. Just let me know what you'd like me to do here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rintaro Just chatted and agree that we shouldn’t deprecate ClassDeclSyntax.name
.
], | ||
children: [ | ||
Child( | ||
name: "actorHeader", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just name this header
. You’re on an actor declaration, so it’s clear that the header is for an actor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is anything in DeclGroupSyntax that allows access to the header, it needs to have a different name from the header children in all of the concrete nodes since it will need to have a broader type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized that as well as I continued reviewing and think this is an argument for not having a DeclHeaderSyntax
node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative I've been considering is making the DeclGroupHeader
node non-base and having it be something like:
Node(
kind: .declGroupHeader,
base: .syntax,
nameForDiagnostics: "declaration group header",
parserFunction: "parseDeclarationGroupHeader",
traits: [
"WithAttributes",
"WithModifiers",
"DeclGroupHeaderProtocol", // slight naming issue I'll have to resolve
],
children: [
Child(
name: "header",
kind: .nodeChoices(choices: [
Child(name: "actorHeader", kind: .node(kind: .actorDeclHeader)),
Child(name: "classHeader", kind: .node(kind: .classDeclHeader)),
Child(name: "enumHeader", kind: .node(kind: .enumDeclHeader)),
Child(name: "extensionHeader", kind: .node(kind: .extensionDeclHeader)),
Child(name: "protocolHeader", kind: .node(kind: .protocolDeclHeader)),
Child(name: "structHeader", kind: .node(kind: .structDeclHeader)),
])
)
]
)
I would then add modifiers
, attributes
, etc. using a handwritten extension switching over the choices, so there'd be no existential overhead. Not quite as elegant, but it still gives us a concrete type that can represent any of the headers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need a DeclGroupHeader
node at all? Correct me if I’m wrong, but I think the primary use for it is to be an option to ABIAttributeArgumentsSyntax
, which already lists all sorts of other declarations. If we listed all the nodes that are subtypes of DeclGroupHeaderSyntax
in there as well, I don’t think there’s real need for DeclGroupHeaderSyntax
anymore. Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also use it in the new implementation for HasTrailingMemberDeclBlock
, and I believe I use it in ASTGen to generalize some of the lowering of DeclGroups, but those probably aren't mandatory—they're just nice little tricks I was able to do now that the new nodes existed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think none of those are sufficient reason to introduce DeclGroupHeaderSyntax
.
case .declGroupHeader: | ||
return "DeclHeaderSyntax" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rename declGroupHeader
to declHeader
so this isn’t needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using "DeclGroupHeader" because only the DeclGroup nodes have separate headers. Do you think I'm being too picky with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just find it add that we have this mismatch here. But depending on how we decide on the existence of DeclHeaderSyntax
, this may be a moot point anyway.
@@ -63,19 +65,25 @@ extension InitSignature { | |||
) | |||
} | |||
|
|||
func transformParam(_ param: FunctionParameterSyntax) -> FunctionParameterSyntax { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func transformParam(_ param: FunctionParameterSyntax) -> FunctionParameterSyntax { | |
func transform(parameter: FunctionParameterSyntax) -> FunctionParameterSyntax { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That feels at odds with how we normally use argument labels because the word parameter
is redundant information given the type. Should it just be transform(_:)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
transform(_:)
seems too ambiguous to me. If you don’t like transform(parameter:)
, I don’t have super strong opinions on it and we can keep transformParam(_:)
allowsMemberBlock: Bool | ||
) -> (RawDeclGroupHeaderSyntax, shouldContinueParsing: Bool) { | ||
func eraseToRawDeclGroupHeaderSyntax( | ||
_ result: (some RawDeclGroupHeaderSyntaxNodeProtocol, Bool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add labels to the tuple elements here? It’s not really obvious what the Bool
represents here.
) -> (RawDeclGroupHeaderSyntax, shouldContinueParsing: Bool) { | ||
func eraseToRawDeclGroupHeaderSyntax( | ||
_ result: (some RawDeclGroupHeaderSyntaxNodeProtocol, Bool) | ||
) -> (RawDeclGroupHeaderSyntax, shouldContinueParsing: Bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, I would prefer to also have a label for the RawDeclGroupHeaderSyntax
.
} | ||
} | ||
|
||
fatalError("Node \(self) does not have a known subtype") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
preconditionFailure
is mildly preferred over fatalError
.
mutating func parseDeclarationGroup<T>( | ||
for header: T, | ||
shouldParseMemberBlock: Bool = true | ||
) -> T.Declaration where T: DeclarationGroupHeaderTrait { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mutating func parseDeclarationGroup<T>( | |
for header: T, | |
shouldParseMemberBlock: Bool = true | |
) -> T.Declaration where T: DeclarationGroupHeaderTrait { | |
mutating func parseDeclarationGroup<T: DeclarationGroupHeaderTrait>( | |
for header: T, | |
shouldParseMemberBlock: Bool = true | |
) -> T.Declaration { |
if shouldParseMemberBlock { | ||
self.parseMemberBlock(introducer: header.introducer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, we can’t use if
expressions yet.
Matches swiftlang/swift#76878.
Note that
@abi
introduces a situation we've never had before: the variousDeclGroupSyntax
decls can now have anil
memberBlock
. This can only occur for children of anABIAttributeArgumentsSyntax
node, not any nodes that would occur in existing constructs. To preserve source compatibility with existing clients, I've madememberBlock
an implicitly-unwrapped optional, but I'm open to opinions if that's not the design we'd like to have.Another place I'd like feedback on is the way I'm parsing malformed
@abi
argument lists. Because the argument is literally an entire decl, I found that the default recovery behavior when the left paren was missing didn't work well, so I built something a little different. If you have better suggestions, I'd love to hear them.Edit: This has been changed from the original implementation. The new version extracts a new "header" child node from nodes like
DeclClassSyntax
which contains everything but the member block. All of theDeclGroup
nodes have this change applied. As proof that this works, I've updated theHasTrailingMemberDeclBlock
initializer to take a header node, rather than a syntax string.Generating the new decls I wanted—particularly their compatibility layers—took substantial refactoring of CodeGenerator, including the introduction of a new
childHistory
mechanism to replacedeprecatedName
and its ilk, extending it with the capability to model this kind of "extraction" of a child node from a parent. I've also made it so that base nodes can have children (these become protocol requirements), traits can optionally declare a memberwise failable initializer (this must be manually implemented at the moment), and deprecated properties are automatically generated for traits.