Skip to content

Commit c4c256a

Browse files
authored
Merge pull request swiftlang#63232 from hborla/member-macro-ast-scope
[ASTScope] Teach ASTScope lookup to find source locations inside macro-expanded scopes.
2 parents 8917692 + 07319d0 commit c4c256a

File tree

6 files changed

+93
-7
lines changed

6 files changed

+93
-7
lines changed

lib/AST/ASTScopeLookup.cpp

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "swift/AST/Stmt.h"
3131
#include "swift/AST/TypeRepr.h"
3232
#include "swift/Basic/STLExtras.h"
33+
#include "swift/Parse/Lexer.h"
3334
#include "llvm/Support/Compiler.h"
3435

3536
using namespace swift;
@@ -90,14 +91,78 @@ static SourceLoc translateLocForReplacedRange(SourceManager &sourceMgr,
9091
NullablePtr<ASTScopeImpl>
9192
ASTScopeImpl::findChildContaining(SourceLoc loc,
9293
SourceManager &sourceMgr) const {
94+
auto *moduleDecl = this->getSourceFile()->getParentModule();
95+
auto *locSourceFile = moduleDecl->getSourceFileContainingLocation(loc);
96+
9397
// Use binary search to find the child that contains this location.
9498
auto *const *child = llvm::lower_bound(
9599
getChildren(), loc,
96-
[&sourceMgr](const ASTScopeImpl *scope, SourceLoc loc) {
100+
[&](const ASTScopeImpl *scope, SourceLoc loc) {
97101
auto rangeOfScope = scope->getCharSourceRangeOfScope(sourceMgr);
98102
ASTScopeAssert(!sourceMgr.isBeforeInBuffer(rangeOfScope.getEnd(),
99103
rangeOfScope.getStart()),
100104
"Source range is backwards");
105+
106+
// If the scope source range and the loc are in two different source
107+
// files, one or both of them are in a macro expansion buffer.
108+
109+
// Note that `scope->getSourceFile()` returns the root of the source tree,
110+
// not the source file containing the location of the ASTScope.
111+
auto scopeStart = scope->getSourceRangeOfThisASTNode().Start;
112+
auto *scopeSourceFile = moduleDecl->getSourceFileContainingLocation(scopeStart);
113+
114+
if (scopeSourceFile != locSourceFile) {
115+
// To compare a source location that is possibly inside a macro expansion
116+
// with a source range that is also possibly in a macro expansion (not
117+
// necessarily the same one as before) we need to find the LCA in the
118+
// source file tree of macro expansions, and compare the original source
119+
// ranges within that common ancestor. We can't walk all the way up to the
120+
// source file containing the parent scope we're searching the children of,
121+
// because two independent (possibly nested) macro expansions can have the
122+
// same original source range in that file; freestanding and peer macros
123+
// mean that we can have arbitrarily nested macro expansions that all add
124+
// declarations to the same scope, that all originate from a single macro
125+
// invocation in the original source file.
126+
127+
// A map from enclosing source files to original source ranges of the macro
128+
// expansions within that file, recording the chain of macro expansions for
129+
// the given scope.
130+
llvm::SmallDenseMap<const SourceFile *, SourceRange> scopeExpansions;
131+
132+
// Walk up the chain of macro expansion buffers for the scope, recording the
133+
// original source range of the macro expansion along the way using generated
134+
// source info.
135+
auto *scopeExpansion = scopeSourceFile;
136+
scopeExpansions[scopeExpansion] = scope->getSourceRangeOfThisASTNode();
137+
while (auto *ancestor = scopeExpansion->getEnclosingSourceFile()) {
138+
auto generatedInfo =
139+
sourceMgr.getGeneratedSourceInfo(*scopeExpansion->getBufferID());
140+
scopeExpansions[ancestor] = generatedInfo->originalSourceRange;
141+
scopeExpansion = ancestor;
142+
}
143+
144+
// Walk up the chain of macro expansion buffers for the source loc we're
145+
// searching for to find the LCA using `scopeExpansions`.
146+
auto *potentialLCA = locSourceFile;
147+
auto expansionLoc = loc;
148+
while (potentialLCA) {
149+
auto scopeExpansion = scopeExpansions.find(potentialLCA);
150+
if (scopeExpansion != scopeExpansions.end()) {
151+
// Take the original expansion range within the LCA of the loc and
152+
// the scope to compare.
153+
rangeOfScope =
154+
Lexer::getCharSourceRangeFromSourceRange(sourceMgr, scopeExpansion->second);
155+
loc = expansionLoc;
156+
break;
157+
}
158+
159+
auto generatedInfo =
160+
sourceMgr.getGeneratedSourceInfo(*potentialLCA->getBufferID());
161+
expansionLoc = generatedInfo->originalSourceRange.Start;
162+
potentialLCA = potentialLCA->getEnclosingSourceFile();
163+
}
164+
}
165+
101166
loc = translateLocForReplacedRange(sourceMgr, rangeOfScope, loc);
102167
return (rangeOfScope.getEnd() == loc ||
103168
sourceMgr.isBeforeInBuffer(rangeOfScope.getEnd(), loc));

lib/AST/ASTVerifier.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3736,6 +3736,18 @@ class Verifier : public ASTWalker {
37363736
return;
37373737
} else if (Decl *D = Parent.getAsDecl()) {
37383738
Enclosing = D->getSourceRange();
3739+
3740+
// If the current source range is in a macro expansion buffer, its enclosing
3741+
// context can be in the source file where the macro expansion originated. In
3742+
// this case, grab the source range of the original ASTNode that was expanded.
3743+
if (!Ctx.SourceMgr.rangeContains(Enclosing, Current)) {
3744+
auto *expansionBuffer =
3745+
D->getModuleContext()->getSourceFileContainingLocation(Current.Start);
3746+
if (auto expansion = expansionBuffer->getMacroExpansion()) {
3747+
Current = expansion.getSourceRange();
3748+
}
3749+
}
3750+
37393751
if (D->isImplicit())
37403752
return;
37413753
// FIXME: This is not working well for decl parents.

lib/AST/DeclContext.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,11 @@ void IterableDeclContext::addMemberSilently(Decl *member, Decl *hint,
984984
if (getASTContext().SourceMgr.isBeforeInBuffer(prevEnd, nextStart))
985985
return;
986986

987+
// Synthesized member macros can add new members in a macro expansion buffer.
988+
auto *memberSourceFile = member->getInnermostDeclContext()->getParentSourceFile();
989+
if (memberSourceFile->getFulfilledMacroRole() == MacroRole::SynthesizedMembers)
990+
return;
991+
987992
llvm::errs() << "Source ranges out of order in addMember():\n";
988993
prev->dump(llvm::errs());
989994
next->dump(llvm::errs());

lib/Sema/TypeCheckMacros.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1230,8 +1230,10 @@ bool swift::expandSynthesizedMembers(CustomAttr *attr, MacroDecl *macro,
12301230
bool synthesizedMembers = false;
12311231
auto topLevelDecls = macroSourceFile->getTopLevelDecls();
12321232
for (auto member : topLevelDecls) {
1233+
// Note that synthesized members are not considered implicit. They have
1234+
// proper source ranges that should be validated, and ASTScope does not
1235+
// expand implicit scopes to the parent scope tree.
12331236
member->setDeclContext(decl->getInnermostDeclContext());
1234-
member->setImplicit();
12351237

12361238
if (auto *nominal = dyn_cast<NominalTypeDecl>(decl)) {
12371239
nominal->addMember(member);

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,15 @@ public struct AddMembers: MemberDeclarationMacro {
360360

361361
let storageVariable: VariableDeclSyntax =
362362
"""
363-
var storage = Storage()
363+
private var storage = Storage()
364364
"""
365365

366366
let instanceMethod: FunctionDeclSyntax =
367367
"""
368-
func method() { print("synthesized method") }
368+
func getStorage() -> Storage {
369+
print("synthesized method")
370+
return storage
371+
}
369372
"""
370373

371374
return [

test/Macros/macro_expand_synthesized_members.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@
1414
@addMembers
1515
struct S {
1616
func useSynthesized() {
17-
print(type(of: storage))
18-
method()
17+
print(type(of: getStorage()))
1918
}
2019
}
2120

2221
let s = S()
2322

24-
// CHECK: Storage
2523
// CHECK: synthesized method
24+
// CHECK: Storage
2625
s.useSynthesized()

0 commit comments

Comments
 (0)