Skip to content

[SourceKit] Support location info for macro-expanded Clang imports #82006

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

Conversation

j-hui
Copy link
Contributor

@j-hui j-hui commented Jun 5, 2025

Currently, when we jump-to-definition for decls that are macro-expanded
from Clang imported decls (e.g., safe overloads generated by
@_SwiftifyImport), setLocationInfo() emits a bongus location pointing to
a generated buffer, leading the IDE to try to jump to a file that does
not exist.

The root cause here is that setLocationInfo() calls getOriginalRange()
(earlier, getOriginalLocation()), which was not written to account for
such cases where a macro is generated from another generated buffer
whose kind is AttributeFromClang.

This patch fixes setLocationInfo() with some refactoring:

  • getOriginalRange() is inlined into setLocationInfo(), so that the
    generated buffer-handling logic is localized to that function. This
    includes how it handles buffers generated for ReplacedFunctionBody.

  • getOriginalLocation() is used in a couple of other places that only
    care about macros expanded from the same buffer (so other generated
    buffers not not relevant). This "macro-chasing" logic is simplified
    and moved from ModuleDecl::getOriginalRange() to a free-standing
    function, getMacroUnexpandedRange() (there is no reason for it to be
    a method of ModuleDecl).

  • GeneratedSourceInfo now carries an extra ClangNode field, which is
    populated by getClangSwiftAttrSourceFile() when constructing
    a generated buffer for an AttributeFromClang. This could probably
    be union'ed with one or more of the other fields in the future.

rdar://151020332

@j-hui
Copy link
Contributor Author

j-hui commented Jun 5, 2025

@swift-ci please test

@j-hui j-hui force-pushed the jump-to-def-for-macro-expanded-clang-imports branch from ccc5e18 to 17ce04f Compare June 6, 2025 00:36
@hamishknight
Copy link
Contributor

getOriginalLocation() is used in a couple of other places that don't
actually care about the original location

I don't think that's true, both getExternalRawLocsForDecl and getMappedLocation use the returned location

case GeneratedSourceInfo::PrettyPrinted:
case GeneratedSourceInfo::DefaultArgument:
case GeneratedSourceInfo::AttributeFromClang:
return SourceRange();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the behavior that we want? What happens when there is a generated default argument inside the macro generated buffer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the current behavior, which I'm trying to preserve.

That said, I'm not sure how we should handle the case you bring up; @bnbarham do you have thoughts on this?

@bnbarham
Copy link
Contributor

bnbarham commented Jun 6, 2025

I don't think that's true, both getExternalRawLocsForDecl and getMappedLocation use the returned location

I believe the full statement is true, ie.

only the outermost buffer a macro is expanded from

At least in the cases we have today (the only original location we care about is the file that the macro was expanded from)

EDIT: Ah, I see, you were referring to the fact that neither getExternalRawLocsForDecl or getMappedLocation were using the returned location 👍

std::tie(UnderlyingBufferID, MainLoc) =
D->getModuleContext()->getOriginalLocation(MainLoc);
if (BufferID != UnderlyingBufferID)
auto R = SM.getUnexpandedMacroRange(MainLoc);
Copy link
Contributor

@bnbarham bnbarham Jun 6, 2025

Choose a reason for hiding this comment

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

I'm fine with the less generic name, but it's unfortunate we have to do another findBufferContainingLoc when we would already have done it in getUnexpandedMacroRange (it's not the cheapest operation ever, though IIRC we do cache the last lookup so... maybe it's fine). Is there any reason not to return the buffer id as well?

But also, this still needs to assign MainLoc if we get a range back

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I figured this should be ok because it's cached:

// Check the last buffer we looked in.
if (auto lastBufferID = LocCache.lastBufferID) {
if (isInBuffer(*lastBufferID))
return *lastBufferID;
}

I'm happy to revert to returning the tuple if you don't want to rely on this.

// restore them if we encounter generated buffer kinds we don't care about.
auto VDRange = range;
auto VDBufferID = bufferID;
while (auto *info = SM.getGeneratedSourceInfo(bufferID)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably worth splitting this one out into its own function here as well IMO

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 ended up refactoring the Location-setting code into a helper (and also simplifying the loop's control flow) so that the generated buffer-chasing logic becomes the focus of setLocationInfo().

@j-hui
Copy link
Contributor Author

j-hui commented Jun 6, 2025

I don't think that's true, both getExternalRawLocsForDecl and getMappedLocation use the returned location

You're right, I somehow missed this entirely 🤦

@j-hui j-hui force-pushed the jump-to-def-for-macro-expanded-clang-imports branch from 17ce04f to 3e5dbf0 Compare June 6, 2025 19:04
@j-hui
Copy link
Contributor Author

j-hui commented Jun 6, 2025

@swift-ci please test

@j-hui j-hui force-pushed the jump-to-def-for-macro-expanded-clang-imports branch from 3e5dbf0 to 3cb05d3 Compare June 6, 2025 20:34
@j-hui
Copy link
Contributor Author

j-hui commented Jun 6, 2025

@swift-ci please test

@j-hui j-hui force-pushed the jump-to-def-for-macro-expanded-clang-imports branch from 3cb05d3 to aa6a39c Compare June 10, 2025 17:06
@j-hui
Copy link
Contributor Author

j-hui commented Jun 10, 2025

@swift-ci please test

@j-hui j-hui force-pushed the jump-to-def-for-macro-expanded-clang-imports branch from aa6a39c to 637c4bf Compare June 10, 2025 19:57
@j-hui
Copy link
Contributor Author

j-hui commented Jun 10, 2025

@swift-ci please test

@j-hui j-hui force-pushed the jump-to-def-for-macro-expanded-clang-imports branch from 637c4bf to 33b80ba Compare June 11, 2025 02:09
@j-hui
Copy link
Contributor Author

j-hui commented Jun 11, 2025

@swift-ci please test

Copy link
Contributor

@hnrklssn hnrklssn left a comment

Choose a reason for hiding this comment

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

LGTM

@j-hui
Copy link
Contributor Author

j-hui commented Jun 11, 2025

@swift-ci please test macos platform

static void setLocationInfoForRange(SourceManager &SM, SourceRange R,
unsigned BufID,
LocationInfo &Location,
bool presumed = false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

There's here and MappedDecl where the parameter name switches casing, would be nice to have them be consistent one way or the other

Copy link
Contributor Author

@j-hui j-hui Jun 13, 2025

Choose a reason for hiding this comment

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

I fixed this up (haven't pushed yet), but the MappedDecl parameter already lives alongside mixed case names: https://github.com/swiftlang/swift/pull/82006/files#diff-d29df7eb941cda5dc4dc36df1f3220fe4468a9b58d4fa8ca251c76337f353589L8672

So maybe I should leave that as is?

Copy link
Contributor

Choose a reason for hiding this comment

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

I fixed this up (haven't pushed yet), but the MappedDecl parameter already lives alongside mixed case names

It was module and MappedDecl is new right? I'd just be locally consistent if nothing else 😅 (so mappedDecl).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, right. But the MappedDecl parameter in getClangSwiftAttrSourceFile() comes from the MappedDecl parameter in importNontrivialAttribute(), which already has it upper-cased. I was aiming to keep the variable name consistent across the function call boundary..

// through other macro expansions. Fall back to setting location based
// on VD's original source range because mapping location back to the
// original file might be tricky.
setLocationInfoForRange(SM, VDRange, VDBufID, Location);
Copy link
Contributor

Choose a reason for hiding this comment

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

What did we do here previously 🤔?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The previous behavior is buried in getOriginalRange(), which is to just bail out and use the original SourceRange:

swift/lib/AST/Module.cpp

Lines 879 to 886 in 00aa22f

case GeneratedSourceInfo::ReplacedFunctionBody:
// There's not really any "original" location for locations within
// replaced function bodies. The body is actually different code to the
// original file.
case GeneratedSourceInfo::PrettyPrinted:
case GeneratedSourceInfo::AttributeFromClang:
// No original location, return the original buffer/location
return {startBufferID, startRange};

Now that you mention this, I'm not sure if that is the correct behavior, but I wasn't sure whether this scenario is even reachable (ReplacedFunctionBody would mostly be for source edits, right? So they shouldn't be embedded within other generated macro buffers?)

Copy link
Contributor

@bnbarham bnbarham Jun 13, 2025

Choose a reason for hiding this comment

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

Yeah, they shouldn't ever be embedded within a macro. TBH I would prefer checking for ReplacedFunctionBody specifically and re-using getUnexpanded... for the macro portion (which would also eliminate the duplicate code there).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Re-using getUnexpanded...() will need special handling for the AttributeFromClang case though, which is unnecessary for the two other call sites. I could push the special case logic in there, but my thinking was that by inlining getUnexpandedMacroRange() here I could keep the complexity close to where the slightly different behavior is needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah... :( Okay

Currently, when we jump-to-definition for decls that are macro-expanded
from Clang imported decls (e.g., safe overloads generated by
@_SwiftifyImport), setLocationInfo() emits a bongus location pointing to
a generated buffer, leading the IDE to try to jump to a file that does
not exist.

The root cause here is that setLocationInfo() calls getOriginalRange()
(earlier, getOriginalLocation()), which was not written to account for
such cases where a macro is generated from another generated buffer
whose kind is 'AttributeFromClang'.

This patch fixes setLocationInfo() with some refactoring:

-   getOriginalRange() is inlined into setLocationInfo(), so that the
    generated buffer-handling logic is localized to that function. This
    includes how it handles buffers generated for ReplacedFunctionBody.

-   getOriginalLocation() is used in a couple of other places that only
    care about macros expanded from the same buffer (so other generated
    buffers not not relevant). This "macro-chasing" logic is simplified
    and moved from ModuleDecl::getOriginalRange() to a free-standing
    function, getMacroUnexpandedRange() (there is no reason for it to be
    a method of ModuleDecl).

-   GeneratedSourceInfo now carries an extra ClangNode field, which is
    populated by getClangSwiftAttrSourceFile() when constructing
    a generated buffer for an 'AttributeFromClang'. This could probably
    be union'ed with one or more of the other fields in the future.

rdar://151020332
@j-hui j-hui force-pushed the jump-to-def-for-macro-expanded-clang-imports branch from 33b80ba to 44aba13 Compare June 13, 2025 01:22
@j-hui
Copy link
Contributor Author

j-hui commented Jun 13, 2025

@swift-ci please test

@j-hui j-hui enabled auto-merge June 13, 2025 01:39
@j-hui
Copy link
Contributor Author

j-hui commented Jun 13, 2025

@swift-ci please test Linux platform

@j-hui
Copy link
Contributor Author

j-hui commented Jun 13, 2025

@swift-ci please test

2 similar comments
@j-hui
Copy link
Contributor Author

j-hui commented Jun 14, 2025

@swift-ci please test

@j-hui
Copy link
Contributor Author

j-hui commented Jun 16, 2025

@swift-ci please test

Copy link
Contributor

@hamishknight hamishknight left a comment

Choose a reason for hiding this comment

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

Thanks!

@j-hui
Copy link
Contributor Author

j-hui commented Jun 18, 2025

@swift-ci please test

1 similar comment
@j-hui
Copy link
Contributor Author

j-hui commented Jun 18, 2025

@swift-ci please test

@j-hui j-hui merged commit a0d3ad7 into swiftlang:main Jun 19, 2025
3 of 5 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.

5 participants