Skip to content

Commit 2bb035e

Browse files
authored
Merge pull request #70126 from DougGregor/source-loc-ordering-with-macros
Implement ordering of source locations that accounts for macro expansion
2 parents ea499a5 + 367a847 commit 2bb035e

File tree

7 files changed

+235
-124
lines changed

7 files changed

+235
-124
lines changed

include/swift/AST/ASTScope.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ class ASTScopeImpl : public ASTAllocated<ASTScopeImpl> {
155155
/// Child scopes, sorted by source range.
156156
Children storedChildren;
157157

158-
mutable llvm::Optional<CharSourceRange> cachedCharSourceRange;
158+
mutable llvm::Optional<SourceRange> cachedCharSourceRange;
159159

160160
#pragma mark - constructor / destructor
161161
public:
@@ -194,8 +194,17 @@ class ASTScopeImpl : public ASTAllocated<ASTScopeImpl> {
194194
#pragma mark - source ranges
195195

196196
public:
197-
CharSourceRange getCharSourceRangeOfScope(SourceManager &SM,
198-
bool omitAssertions = false) const;
197+
/// Retrieve the source range of the given scope, where the end location
198+
/// is adjusted to refer to the end of the token.
199+
///
200+
/// Since the adjustment to the end of the token requires lexing, this
201+
/// routine also caches the result.
202+
///
203+
/// Note that the start and end locations might be in different source
204+
/// buffers, so we represent the result as SourceRange rather than
205+
/// CharSourceRange.
206+
SourceRange getCharSourceRangeOfScope(SourceManager &SM,
207+
bool omitAssertions = false) const;
199208
bool isCharSourceRangeCached() const;
200209

201210
/// Returns source range of this node alone, without factoring in any

include/swift/Basic/SourceManager.h

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "swift/Basic/FileSystem.h"
1717
#include "swift/Basic/SourceLoc.h"
1818
#include "clang/Basic/FileManager.h"
19+
#include "llvm/ADT/ArrayRef.h"
1920
#include "llvm/ADT/DenseSet.h"
2021
#include "llvm/ADT/None.h"
2122
#include "llvm/ADT/Optional.h"
@@ -69,6 +70,10 @@ class GeneratedSourceInfo {
6970
/// The name of the source file on disk that was created to hold the
7071
/// contents of this file for external clients.
7172
StringRef onDiskBufferCopyFileName = StringRef();
73+
74+
/// Contains the ancestors of this source buffer, starting with the root source
75+
/// buffer and ending at this source buffer.
76+
mutable llvm::ArrayRef<unsigned> ancestors = llvm::ArrayRef<unsigned>();
7277
};
7378

7479
/// This class manages and owns source buffers.
@@ -205,6 +210,15 @@ class SourceManager {
205210
llvm::Optional<GeneratedSourceInfo>
206211
getGeneratedSourceInfo(unsigned bufferID) const;
207212

213+
/// Retrieve the list of ancestors of the given source buffer, starting with
214+
/// the root buffer and proceding to the given buffer ID at the end.
215+
///
216+
/// The scratch parameter will be used to avoid allocation in the case where
217+
/// the given buffer ID is the top-level buffer, in which case bufferID will
218+
/// be written into scratch at the returned array will contain that one
219+
/// element.
220+
ArrayRef<unsigned> getAncestors(unsigned bufferID, unsigned &scratch) const;
221+
208222
/// Record the starting source location of a regex literal.
209223
void recordRegexLiteralStartLoc(SourceLoc loc) {
210224
RegexLiteralStartLocs.insert(loc);
@@ -216,13 +230,49 @@ class SourceManager {
216230
return RegexLiteralStartLocs.contains(loc);
217231
}
218232

219-
/// Returns true if \c LHS is before \c RHS in the source buffer.
233+
/// Returns true if \c first is before \c second in the same source file,
234+
/// accounting for the possibility that first and second are in different
235+
/// source buffers that are conceptually within the same source file,
236+
/// due to macro expansion.
237+
bool isBefore(SourceLoc first, SourceLoc second) const;
238+
239+
/// Returns true if \c first is at or before \c second in the same source
240+
/// file, accounting for the possibility that first and second are in
241+
/// different source buffers that are conceptually within the same source
242+
/// file, due to macro expansion.
243+
bool isAtOrBefore(SourceLoc first, SourceLoc second) const;
244+
245+
/// Returns true if \c LHS is before \c RHS in the same source buffer.
220246
bool isBeforeInBuffer(SourceLoc LHS, SourceLoc RHS) const {
221247
return LHS.Value.getPointer() < RHS.Value.getPointer();
222248
}
223249

224-
/// Returns true if range \c R contains the location \c Loc. The location
225-
/// \c Loc should point at the beginning of the token.
250+
/// Returns true if \c range contains the location \c loc. The location
251+
/// \c loc should point at the beginning of the token.
252+
///
253+
/// This function accounts for the possibility that the source locations
254+
/// provided might come from different source buffers that are conceptually
255+
/// part of the same source file, for example due to macro expansion.
256+
bool containsTokenLoc(SourceRange range, SourceLoc loc) const;
257+
258+
/// Returns true if \c range contains the location \c loc.
259+
///
260+
/// This function accounts for the possibility that the source locations
261+
/// provided might come from different source buffers that are conceptually
262+
/// part of the same source file, for example due to macro expansion.
263+
bool containsLoc(SourceRange range, SourceLoc loc) const;
264+
265+
/// Returns true if \c enclosing contains the whole range \c inner.
266+
///
267+
/// This function accounts for the possibility that the source locations
268+
/// provided might come from different source buffers that are conceptually
269+
/// part of the same source file, for example due to macro expansion.
270+
bool encloses(SourceRange enclosing, SourceRange inner) const;
271+
272+
/// Returns true if range \c R contains the location \c Loc, where all
273+
/// locations are known to be in the same source buffer.
274+
///
275+
/// The location \c Loc should point at the beginning of the token.
226276
bool rangeContainsTokenLoc(SourceRange R, SourceLoc Loc) const {
227277
return Loc == R.Start || Loc == R.End ||
228278
(isBeforeInBuffer(R.Start, Loc) && isBeforeInBuffer(Loc, R.End));

lib/AST/ASTScopeLookup.cpp

Lines changed: 29 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,26 @@ void ASTScopeImpl::unqualifiedLookup(
4747

4848
const ASTScopeImpl *ASTScopeImpl::findStartingScopeForLookup(
4949
SourceFile *sourceFile, const SourceLoc loc) {
50-
auto *const fileScope = sourceFile->getScope().impl;
50+
auto *fileScope = sourceFile->getScope().impl;
51+
52+
// Workaround for bad locations; just return the file scope.
53+
if (loc.isInvalid())
54+
return fileScope;
55+
56+
// Some callers get the actual source file wrong. Look for the actual
57+
// source file containing this location.
58+
auto actualSF =
59+
sourceFile->getParentModule()->getSourceFileContainingLocation(loc);
60+
61+
// If there is no source file containing this location, just return the
62+
// scope we have.
63+
if (!actualSF)
64+
return fileScope;
65+
66+
// Grab the new file scope.
67+
if (actualSF != sourceFile)
68+
fileScope = actualSF->getScope().impl;
69+
5170
const auto *innermost = fileScope->findInnermostEnclosingScope(
5271
sourceFile->getParentModule(), loc, nullptr);
5372
ASTScopeAssert(innermost->getWasExpanded(),
@@ -79,11 +98,11 @@ ASTScopeImpl *ASTScopeImpl::findInnermostEnclosingScopeImpl(
7998
/// If the \p loc is in a new buffer but \p range is not, consider the location
8099
/// is at the start of replaced range. Otherwise, returns \p loc as is.
81100
static SourceLoc translateLocForReplacedRange(SourceManager &sourceMgr,
82-
CharSourceRange range,
101+
SourceLoc rangeStart,
83102
SourceLoc loc) {
84103
for (const auto &pair : sourceMgr.getReplacedRanges()) {
85104
if (sourceMgr.rangeContainsTokenLoc(pair.second, loc) &&
86-
!sourceMgr.rangeContainsTokenLoc(pair.second, range.getStart())) {
105+
!sourceMgr.rangeContainsTokenLoc(pair.second, rangeStart)) {
87106
return pair.first.Start;
88107
}
89108
}
@@ -94,89 +113,23 @@ NullablePtr<ASTScopeImpl>
94113
ASTScopeImpl::findChildContaining(ModuleDecl *parentModule,
95114
SourceLoc loc,
96115
SourceManager &sourceMgr) const {
97-
auto *locSourceFile = parentModule->getSourceFileContainingLocation(loc);
116+
if (loc.isInvalid())
117+
return nullptr;
98118

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

176128
if (child != getChildren().end()) {
177129
auto rangeOfScope = (*child)->getCharSourceRangeOfScope(sourceMgr);
178-
loc = translateLocForReplacedRange(sourceMgr, rangeOfScope, loc);
179-
if (rangeOfScope.contains(loc))
130+
loc = translateLocForReplacedRange(sourceMgr, rangeOfScope.Start, loc);
131+
if (sourceMgr.isAtOrBefore(rangeOfScope.Start, loc) &&
132+
sourceMgr.isBefore(loc, rangeOfScope.End))
180133
return *child;
181134
}
182135

lib/AST/ASTScopeSourceRange.cpp

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ using namespace ast_scope;
3838

3939
static SourceLoc getLocAfterExtendedNominal(const ExtensionDecl *);
4040

41+
/// Retrieve the character-based source range for the given source range.
42+
static SourceRange getCharSourceRange(
43+
SourceManager &sourceMgr, SourceRange range
44+
){
45+
range.End = Lexer::getLocForEndOfToken(sourceMgr, range.End);
46+
return range;
47+
}
48+
4149
void ASTScopeImpl::checkSourceRangeBeforeAddingChild(ASTScopeImpl *child,
4250
const ASTContext &ctx) const {
4351
// Ignore debugger bindings - they're a special mix of user code and implicit
@@ -51,35 +59,18 @@ void ASTScopeImpl::checkSourceRangeBeforeAddingChild(ASTScopeImpl *child,
5159

5260
auto range = getCharSourceRangeOfScope(sourceMgr);
5361

54-
std::function<bool(CharSourceRange)> containedInParent;
55-
containedInParent = [&](CharSourceRange childCharRange) {
62+
std::function<bool(SourceRange)> containedInParent;
63+
containedInParent = [&](SourceRange childCharRange) {
5664
// HACK: For code completion. Handle replaced range.
5765
for (const auto &pair : sourceMgr.getReplacedRanges()) {
58-
auto originalRange =
59-
Lexer::getCharSourceRangeFromSourceRange(sourceMgr, pair.first);
60-
auto newRange =
61-
Lexer::getCharSourceRangeFromSourceRange(sourceMgr, pair.second);
62-
if (range.contains(originalRange) &&
63-
newRange.contains(childCharRange))
66+
auto originalRange = getCharSourceRange(sourceMgr, pair.first);
67+
auto newRange = getCharSourceRange(sourceMgr, pair.second);
68+
if (sourceMgr.encloses(range, originalRange) &&
69+
sourceMgr.encloses(newRange, childCharRange))
6470
return true;
6571
}
6672

67-
if (range.contains(childCharRange))
68-
return true;
69-
70-
// If this is from a macro expansion, look at the where the expansion
71-
// occurred.
72-
auto childBufferID =
73-
sourceMgr.findBufferContainingLoc(childCharRange.getStart());
74-
auto generatedInfo = sourceMgr.getGeneratedSourceInfo(childBufferID);
75-
if (!generatedInfo)
76-
return false;
77-
78-
CharSourceRange expansionRange = generatedInfo->originalSourceRange;
79-
if (expansionRange.isInvalid())
80-
return false;
81-
82-
return containedInParent(expansionRange);
73+
return sourceMgr.encloses(range, childCharRange);
8374
};
8475

8576
auto childCharRange = child->getCharSourceRangeOfScope(sourceMgr);
@@ -95,11 +86,9 @@ void ASTScopeImpl::checkSourceRangeBeforeAddingChild(ASTScopeImpl *child,
9586
if (!storedChildren.empty()) {
9687
auto previousChild = storedChildren.back();
9788
auto endOfPreviousChild = previousChild->getCharSourceRangeOfScope(
98-
sourceMgr).getEnd();
89+
sourceMgr).End;
9990

100-
if (childCharRange.getStart() != endOfPreviousChild &&
101-
!sourceMgr.isBeforeInBuffer(endOfPreviousChild,
102-
childCharRange.getStart())) {
91+
if (!sourceMgr.isAtOrBefore(endOfPreviousChild, childCharRange.Start)) {
10392
auto &out = verificationError() << "child overlaps previous child:\n";
10493
child->print(out);
10594
out << "\n***Previous child\n";
@@ -384,18 +373,19 @@ SourceRange GuardStmtBodyScope::getSourceRangeOfThisASTNode(
384373

385374
#pragma mark source range caching
386375

387-
CharSourceRange
376+
SourceRange
388377
ASTScopeImpl::getCharSourceRangeOfScope(SourceManager &SM,
389378
bool omitAssertions) const {
390379
if (!isCharSourceRangeCached()) {
391380
auto range = getSourceRangeOfThisASTNode(omitAssertions);
392381
ASTScopeAssert(range.isValid(), "scope has invalid source range");
393-
ASTScopeAssert(SM.isBeforeInBuffer(range.Start, range.End) ||
382+
ASTScopeAssert(SM.isBefore(range.Start, range.End) ||
394383
range.Start == range.End,
395384
"scope source range ends before start");
396385

397-
cachedCharSourceRange =
398-
Lexer::getCharSourceRangeFromSourceRange(SM, range);
386+
range.End = Lexer::getLocForEndOfToken(SM, range.End);
387+
388+
cachedCharSourceRange = range;
399389
}
400390

401391
return *cachedCharSourceRange;

lib/AST/DeclContext.cpp

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,16 +1026,7 @@ void IterableDeclContext::addMemberSilently(Decl *member, Decl *hint,
10261026
assert(nextStart.isValid() &&
10271027
"Only implicit decls can have invalid source location");
10281028

1029-
if (getASTContext().SourceMgr.isBeforeInBuffer(prevEnd, nextStart))
1030-
return;
1031-
1032-
// Synthesized member macros can add new members in a macro expansion buffer.
1033-
SourceFile *memberSourceFile = member->getLoc()
1034-
? member->getModuleContext()
1035-
->getSourceFileContainingLocation(member->getLoc())
1036-
: member->getInnermostDeclContext()->getParentSourceFile();
1037-
if (memberSourceFile->getFulfilledMacroRole() == MacroRole::Member ||
1038-
memberSourceFile->getFulfilledMacroRole() == MacroRole::Peer)
1029+
if (getASTContext().SourceMgr.isAtOrBefore(prevEnd, nextStart))
10391030
return;
10401031

10411032
llvm::errs() << "Source ranges out of order in addMember():\n";

lib/AST/NameLookup.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,15 @@ bool
16651665
namelookup::isInMacroArgument(SourceFile *sourceFile, SourceLoc loc) {
16661666
bool inMacroArgument = false;
16671667

1668+
// Make sure that the source location is actually within the given source
1669+
// file.
1670+
if (sourceFile && loc.isValid()) {
1671+
sourceFile =
1672+
sourceFile->getParentModule()->getSourceFileContainingLocation(loc);
1673+
if (!sourceFile)
1674+
return false;
1675+
}
1676+
16681677
ASTScope::lookupEnclosingMacroScope(
16691678
sourceFile, loc,
16701679
[&](auto potentialMacro) -> bool {

0 commit comments

Comments
 (0)