swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
66.79k stars 10.29k forks source link

Macros seem to be expanded in their own sub-scope #73707

Open tmcdonell opened 1 month ago

tmcdonell commented 1 month ago

Description

Code item macros seem to be expanded in their own scope, rather than into the surrounding scope they were called in. This means that operations such as defer will be executed immediately, rather than at the end of the enclosing scope as one would expect (and as described in the official example here).

Reproduction

Minimal reproducer is here: https://github.com/tmcdonell/swift-issue-73707-example

The macro adds a defer { print( ... ) } statement that should be printed right before the function exits.

> swift run
Building for debugging...
[7/7] Applying DeferClient
Build of product 'DeferClient' complete! (0.46s)
=== Expected ===
hello, sailor
exiting function expected()
=== Actual ===
exiting function actual()
hello, sailor

Expected behavior

The deferred print statement should be executed at the end of the function scope.

Environment

Tested with:

Target: arm64-apple-macosx14.0

Additional information

Tangentially related: https://github.com/apple/swift/issues/72307 Since 5.10 it is (correctly) no longer possible to work around this using a declaration macro

tmcdonell commented 1 month ago

Correction, it seems like using a freestanding declaration method never worked either. I just tested the example from apple/swift#72307 and that also executes the defer statement immediately (swift-5.9-release). Same as if I change this example to a declaration macro.

@i2h3 if I just add some print statements to your example I get the following:

> swift run
Building for debugging...
[8/8] Linking SignpostClient
Build complete! (1.49s)
Signpost begin: sayHello()
Signpost defer: sayHello()
Hello!

Did you also encounter this behaviour (in your larger application)?

Edit: For completeness, here is the diff:

diff --git a/Sources/SignpostMacros/SignpostMacro.swift b/Sources/SignpostMacros/SignpostMacro.swift
index c5fb8a8..b9a3677 100644
--- a/Sources/SignpostMacros/SignpostMacro.swift
+++ b/Sources/SignpostMacros/SignpostMacro.swift
@@ -10,9 +10,11 @@ public struct SignpostMacro: DeclarationMacro {
                 stringLiteral: """
                 let signpostID = signposter.makeSignpostID()
                 let signpostIntervalState = signposter.beginInterval(#function, id: signpostID)
+                print("Signpost begin: \\(#function)")

                 defer {
                     signposter.endInterval(#function, signpostIntervalState)
+                    print("Signpost defer: \\(#function)")
                 }
                 """
             ),
tmcdonell commented 1 month ago

According to the description of how macros are expanded, it seems that point (3) is not accurate; the compiler does not replace the macro call with its expanded form, rather it puts the expansion in a sub-tree and replaces the call with that. The diagram above this list also seems to imply the expansion should happen inline, but that doesn't appear to be the behaviour I am observing. Or, am I just doing something wrong? Any advice?

i2h3 commented 1 month ago

Did you also encounter this behaviour (in your larger application)?

I am sorry, I did not notice it and cannot tell by now because I removed all macro-based logging from our codebase shortly after to move on.

tmcdonell commented 1 month ago

I am sorry, I did not notice it and cannot tell by now because I removed all macro-based logging from our codebase shortly after to move on.

Okay no problem, thank you for letting me know! (: I also had to abandon macros from our codebase to move on...