Skip to content

Remove the with<childName> functions on syntax nodes #1253

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 1 commit into from
Jan 26, 2023

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Jan 20, 2023

Instead, provide a single with function that takes a key path of the child you want to replace. This significatnly reduces the amout of code that is being generated for SwiftSyntax.

While at it, also remove the without(Leading|Trailing)?Trivia functions.

@DougGregor I changed a few calls of token.withoutTrivia() in macros to token.text, which should perform the same thing while also being slightly more efficient.

@ahoppen ahoppen requested a review from bnbarham January 20, 2023 16:21
@ahoppen ahoppen requested a review from DougGregor as a code owner January 20, 2023 16:21
@ahoppen
Copy link
Member Author

ahoppen commented Jan 20, 2023

@swift-ci Please test

@ahoppen ahoppen changed the title Remove the with<childName> functions on syntax nodes Remove the with<childName> functions on syntax nodes 🚥 #1252 Jan 20, 2023
@ahoppen
Copy link
Member Author

ahoppen commented Jan 20, 2023

@@ -71,7 +71,7 @@ extension SyntaxProtocol {
/// diagnostic message), return that.
/// Otherwise, return a generic message that describes the tokens in this node.
var shortSingleLineContentDescription: String {
let contentWithoutTrivia = self.withoutLeadingTrivia().withoutTrailingTrivia().description
let contentWithoutTrivia = self.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it would be worth having a description without trivia, seems wasteful to create a new node (twice) just to remove trivia for printing.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, it seems wasteful but it seems like a fairly narrow use-case and I don’t want to blow up our API surface for such use cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't seem that narrow. Can leave for now, we can always add later.

Copy link
Contributor

Choose a reason for hiding this comment

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

Drive-by comment: it's useful for SwiftLint, we currently have 13 occurrences of withoutTrivia().description https://github.com/search?q=repo%3Arealm%2FSwiftLint%20withoutTrivia().description&type=code

Maybe there's a better way to accomplish this, but that's what we're doing now.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, you convinced me. I added trimmedDescription. I decided against naming it descriptionWithoutTrivia because that sounds like we would also remove trivia in the middle of the node.

Comment on lines -197 to +204
self = with${child.name}(value)
let arena = SyntaxArena()
% if child.is_optional:
let raw = value?.raw
% else:
let raw = value.raw
% end
let newData = data.replacingChild(at: ${idx}, with: raw, arena: arena)
self = ${node.name}(newData)
Copy link
Contributor

@bnbarham bnbarham Jan 20, 2023

Choose a reason for hiding this comment

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

Ah, I was hoping we could use SyntaxNodeStructure and the need for this entirely. We don't currently have the reverse mapping (ie. keypath -> index), but maybe that would be useful. That would be even more codegen though, not less 😅.

EDIT: We will need that mapping when we eventually make this variadic + want to set multiple in with(...) at once though.

Copy link
Member Author

Choose a reason for hiding this comment

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

How would you want to get rid of these using SyntaxNodeStructure?

Copy link
Contributor

Choose a reason for hiding this comment

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

You can't right, we need the reverse mapping (key path -> index).

@_disfavoredOverload
func with<T>(_ keyPath: WritableKeyPath<\(raw: trait.traitName)Syntax, T>, _ newChild: T) -> \(raw: trait.traitName)Syntax {
var copy: \(raw: trait.traitName)Syntax = self
copy[keyPath: keyPath] = newChild
Copy link
Contributor

Choose a reason for hiding this comment

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

layoutKeyPath or something instead? keyPath: keyPath just looks weird 😅

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, it’s not just layouts right. keyPath can also point to trivia.

@rintaro
Copy link
Member

rintaro commented Jan 20, 2023

I know we decided to do with() accepting keypaths. But maybe resurrecting Cursor (but with associated values) might be an option?
For example:

extension FunctionDeclSyntax {
  enum Cursor {
    case attributes(AttributeListSyntax)
    case modifiers(ModifierListSyntax?)
    ...
  }
  func with(_ values: Cursor...) -> Self {
    ...
    for cursor in values {
      switch cursor {
      case .attributes(let val): layout[0] = val.raw
      case .modifiers(let val): layout[1] = val?.raw
      ...
      }
    }
    ...
  }
}

So you can call foo = bar.with(.attribute(newAttr), .signature(rewritten))

@rintaro
Copy link
Member

rintaro commented Jan 21, 2023

A concern accepting KeyPaths on Self is that it need to accept any properties including that is not meant to. For example:

newFn = fn.with(\.self, otherFn)

It works, but not sure we should accept this.

Also do we want to accept multi-level paths? fn.with(\.signature.input, newInput). This looks cool, so I think the answer is yes.

@ahoppen
Copy link
Member Author

ahoppen commented Jan 23, 2023

I know we decided to do with() accepting keypaths. But maybe resurrecting Cursor (but with associated values) might be an option?

That would just be trading some code generation for another. What I like about the key path approach is that it doesn’t require code generation.

A concern accepting KeyPaths on Self is that it need to accept any properties including that is not meant to. For example:

newFn = fn.with(\.self, otherFn)

It works, but not sure we should accept this.

Also do we want to accept multi-level paths? fn.with(\.signature.input, newInput). This looks cool, so I think the answer is yes.

I actually really liked how the with function generalized from layouts to trivia as well. And it’s really just a shorthand to write three lines in one, so I’m totally happy if it generalizes to multi-level key paths. And I don’t see any problem with fn.with(\.self, otherFn) working as well. Sure, it’s silly but it does exactly what you expect, right?

@ahoppen ahoppen changed the title Remove the with<childName> functions on syntax nodes 🚥 #1252 Remove the with<childName> functions on syntax nodes Jan 23, 2023
@ahoppen ahoppen force-pushed the ahoppen/remove-with-functions branch 3 times, most recently from 0c105f1 to a63faeb Compare January 24, 2023 22:55
@ahoppen
Copy link
Member Author

ahoppen commented Jan 24, 2023

1 similar comment
@ahoppen
Copy link
Member Author

ahoppen commented Jan 25, 2023

@ahoppen
Copy link
Member Author

ahoppen commented Jan 25, 2023

Instead, provide a single `with` function that takes a key path of the child you want to replace. This significatnly reduces the amout of code that is being generated for SwiftSyntax.

While at it, also remove the `without(Leading|Trailing)?Trivia` functions
@ahoppen ahoppen force-pushed the ahoppen/remove-with-functions branch from 1342762 to 1940941 Compare January 26, 2023 07:32
@ahoppen
Copy link
Member Author

ahoppen commented Jan 26, 2023

@kimdv kimdv mentioned this pull request Jan 26, 2023
@ahoppen
Copy link
Member Author

ahoppen commented Jan 26, 2023

@ahoppen ahoppen merged commit 00c6476 into swiftlang:main Jan 26, 2023
@ahoppen ahoppen deleted the ahoppen/remove-with-functions branch January 26, 2023 18:46
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.

4 participants