Skip to content
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

MacroExpansionContext.lexicalContext always empty for freestanding macros? #2907

Closed
Jeehut opened this issue Dec 2, 2024 · 5 comments
Closed
Labels
bug Something isn't working

Comments

@Jeehut
Copy link
Contributor

Jeehut commented Dec 2, 2024

Description

I am currently writing a freestanding macro using version 600.0.1 which needs to read some information about its enclosing code. According to this thread on the forums @DougGregor has implemented exactly what I need in #1554 inside the lexicalContext property. But when I call context.lexicalContext I always get an empty array, even though my freestanding macro is placed inside a SwiftUI body within a Button initializer like this:

import SwiftUI
      
struct MyView: View {
   var body: some View {
      Button(#tk("Save Changes")) { 
         self.handleSave()
      }
   }
}

The purpose of my macro is to auto-generate a semantic localization key based on the context. The expected expansion:

import SwiftUI

struct MyView: View {
   var body: some View {
      Button(String(localized: "MyView.Body.Button.saveChanges", defaultValue: "Save Changes")) { 
         self.handleSave()
      }
   }
}

I got everything working, except that I can't access the context. I would expect lexicalContext to be something like ["Button", "body", "MyView"] or at least contain these texts in some kind of structure so I can read them out. But it's completely empty for me. Am I missing something?

Steps to Reproduce

This is the helper function in my ExpressionMacro I'm trying to implement:

   /// Returns `MyView.body.Button.SaveChanges` for the following view:
   /// ```swift
   /// struct MyView: View {
   ///    var body: some View {
   ///       Button(#tk("Save Changes")) {
   ///          self.handleSave()
   ///       }
   ///    }
   /// }
   /// ```
   /// Or `MyModel.DisplayName.Movie.movie` and `MyModel.DisplayName.Series.tvShow` for the following model:
   /// ```swift
   /// enum MyModel: String, CaseIterable, Codable {
   ///    case movie
   ///    case series
   ///
   ///    var displayName: String {
   ///       switch self {
   ///       case .movie: #tk("Movie")
   ///       case .series: #tk("TV Show")
   ///       }
   ///    }
   /// }
   /// ```
   /// - NOTE: Use https://swift-ast-explorer.com for development.
   private static func semanticKey(
      of node: some FreestandingMacroExpansionSyntax,
      in context: some MacroExpansionContext
   ) -> String {
      // node-based approach
      return node.parent?.description ?? "N/A"  // always returns `"N/A"`

      // context-based approach
      return context.lexicalContext.description  // always returns `"[]"`
   }
@Jeehut Jeehut added the bug Something isn't working label Dec 2, 2024
@ahoppen
Copy link
Member

ahoppen commented Dec 3, 2024

Synced to Apple’s issue tracker as rdar://140829016

@ahoppen
Copy link
Member

ahoppen commented Dec 3, 2024

Are you seeing the empty lexical context when testing the macro using assertMacroExpansion and/or when expanding the macro during compilation?

@Jeehut
Copy link
Contributor Author

Jeehut commented Dec 4, 2024

I'm using the swift-macro-testing library of point-free in my tests, because when I wrote tests with assertMacroExpansion the tests never failed even when I had not written any code yet inside my expression macro implementation. I don't know if I did something wrong, but everything immediately worked as expected with their library.

But I did try it in a real-world project too, and the result was exactly the same as with the macro-testing library.

For your convenience, I attached a full minimal Swift package with which you can reproduce the exact problem I'm reporting here by simply running the tests. I've also added an assertMacroExpansion version for you, but like I said, they're pretty much useless because they're never failing:
TranslateKit-Issue-2907.zip

@ahoppen
Copy link
Member

ahoppen commented Dec 4, 2024

Thanks for providing the reproducing project. It looks like there are two independent issues:

  1. In order to receive diagnostics from assertMacroExpansion when using swift-testing, you need to import SwiftSyntaxMacrosGenericTestSupport and provide a failureHandler as follows. With that, you see the correct lexical context (and some errors in your macro because context.lexicalContext.description is multiline and thus doesn’t fit into the single-line string literal that you created. https://github.com/swiftlang/swift-syntax/blob/main/Release%20Notes/600.md has some details about SwiftSyntaxMacrosGenericTestSupport and Adopt Swift-Testing in test utils such as SwiftSyntaxMacrosTestSupport #2720 is tracking that you shouldn’t need to specify a failure handler
failureHandler: { Issue.record("\($0.message)", sourceLocation: SourceLocation(fileID: $0.location.fileID, filePath: $0.location.filePath, line: $0.location.line, column: $0.location.column)) }
  1. It appears that MacroTesting is not passing in the lexical context through. That sounds like an issue with the swift-macro-testing library, not with swift-syntax.

I’m going to close this issue because we are already tracking (1) and (2) is not an issue with swift-syntax itself. Feel free to re-open it if I missed anything.

@ahoppen ahoppen closed this as completed Dec 4, 2024
@Jeehut
Copy link
Contributor Author

Jeehut commented Dec 5, 2024

@ahoppen Thank you for the quick response and the explanation!

After you said that the MacroTesting library is not passing the lexical context, I wanted to report it in their repo and found that somebody had already run into this and fixed it in PR pointfreeco/swift-macro-testing#31. So I switched the lib to the main branch and boom, everything works as expected!

Of course, returning context.lexicalContext.description wasn't quite right, but I didn't know what to return since I always got an empty array, so I didn't know what to work with. But now I'm getting a bunch of context, so I can finally try to see if I can auto-generate a useful localization key. Thank you for your quick help! 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants