Skip to content

Commit 6a58991

Browse files
committed
[Parse] Store #if ranges on the SourceFile
Record any `#if`'s we encounter, and make a note of their active region.
1 parent 26a04fe commit 6a58991

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

include/swift/AST/SourceFile.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,39 @@ enum class RestrictedImportKind {
4848
/// Import that limits the access level of imported entities.
4949
using ImportAccessLevel = llvm::Optional<AttributedImport<ImportedModule>>;
5050

51+
/// Stores range information for a \c #if block in a SourceFile.
52+
class IfConfigRangeInfo final {
53+
/// The range of the entire \c #if block, including \c #else and \c #endif.
54+
CharSourceRange WholeRange;
55+
56+
/// The range of the active selected body, if there is one. This does not
57+
/// include the outer syntax of the \c #if. This may be invalid, which
58+
/// indicates there is no active body.
59+
CharSourceRange ActiveBodyRange;
60+
61+
public:
62+
IfConfigRangeInfo(CharSourceRange wholeRange, CharSourceRange activeBodyRange)
63+
: WholeRange(wholeRange), ActiveBodyRange(activeBodyRange) {
64+
assert(wholeRange.getByteLength() > 0 && "Range must be non-empty");
65+
assert(activeBodyRange.isInvalid() || wholeRange.contains(activeBodyRange));
66+
}
67+
68+
CharSourceRange getWholeRange() const { return WholeRange; }
69+
SourceLoc getStartLoc() const { return WholeRange.getStart(); }
70+
71+
friend bool operator==(const IfConfigRangeInfo &lhs,
72+
const IfConfigRangeInfo &rhs) {
73+
return lhs.WholeRange == rhs.WholeRange &&
74+
lhs.ActiveBodyRange == rhs.ActiveBodyRange;
75+
}
76+
77+
/// Retrieve the ranges produced by subtracting the active body range from
78+
/// the whole range. This includes both inactive branches as well as the
79+
/// other syntax of the \c #if.
80+
SmallVector<CharSourceRange, 2>
81+
getRangesWithoutActiveBody(const SourceManager &SM) const;
82+
};
83+
5184
/// A file containing Swift source code.
5285
///
5386
/// This is a .swift or .sil file (or a virtual file, such as the contents of
@@ -209,6 +242,19 @@ class SourceFile final : public FileUnit {
209242
ParserStatePtr DelayedParserState =
210243
ParserStatePtr(/*ptr*/ nullptr, /*deleter*/ nullptr);
211244

245+
struct IfConfigRangesData {
246+
/// All the \c #if source ranges in this file.
247+
std::vector<IfConfigRangeInfo> Ranges;
248+
249+
/// Whether the elemnts in \c Ranges are sorted in source order within
250+
/// this file. We flip this to \c false any time a new range gets recorded,
251+
/// and lazily do the sorting when doing a query.
252+
bool IsSorted = false;
253+
};
254+
255+
/// Stores all the \c #if source range info in this file.
256+
mutable IfConfigRangesData IfConfigRanges;
257+
212258
friend class HasImportsMatchingFlagRequest;
213259

214260
/// Indicates which import options have valid caches. Storage for
@@ -448,6 +494,13 @@ class SourceFile final : public FileUnit {
448494
const_cast<SourceFile *>(this)->MissingImportedModules.insert(module);
449495
}
450496

497+
/// Record the source range info for a parsed \c #if block.
498+
void recordIfConfigRangeInfo(IfConfigRangeInfo ranges);
499+
500+
/// Retrieve the source range infos for any \c #if blocks contained within a
501+
/// given source range of this file.
502+
ArrayRef<IfConfigRangeInfo> getIfConfigsWithin(SourceRange outer) const;
503+
451504
void getMissingImportedModules(
452505
SmallVectorImpl<ImportedModule> &imports) const override;
453506

lib/AST/Module.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3326,6 +3326,70 @@ SourceFile::getImportAccessLevel(const ModuleDecl *targetModule) const {
33263326
return restrictiveImport;
33273327
}
33283328

3329+
SmallVector<CharSourceRange, 2>
3330+
IfConfigRangeInfo::getRangesWithoutActiveBody(const SourceManager &SM) const {
3331+
SmallVector<CharSourceRange, 2> result;
3332+
if (ActiveBodyRange.isValid()) {
3333+
// Split the whole range by the active range.
3334+
result.emplace_back(SM, WholeRange.getStart(), ActiveBodyRange.getStart());
3335+
result.emplace_back(SM, ActiveBodyRange.getEnd(), WholeRange.getEnd());
3336+
} else {
3337+
// No active body, we just return the whole range.
3338+
result.push_back(WholeRange);
3339+
}
3340+
return result;
3341+
}
3342+
3343+
void SourceFile::recordIfConfigRangeInfo(IfConfigRangeInfo ranges) {
3344+
IfConfigRanges.Ranges.push_back(ranges);
3345+
IfConfigRanges.IsSorted = false;
3346+
}
3347+
3348+
ArrayRef<IfConfigRangeInfo>
3349+
SourceFile::getIfConfigsWithin(SourceRange outer) const {
3350+
auto &SM = getASTContext().SourceMgr;
3351+
assert(SM.getRangeForBuffer(BufferID).contains(outer.Start) &&
3352+
"Range not within this file?");
3353+
3354+
if (!IfConfigRanges.IsSorted) {
3355+
// Sort the ranges if we need to.
3356+
llvm::sort(IfConfigRanges.Ranges, [&](IfConfigRangeInfo lhs,
3357+
IfConfigRangeInfo rhs) {
3358+
return SM.isBeforeInBuffer(lhs.getStartLoc(), rhs.getStartLoc());
3359+
});
3360+
3361+
// Be defensive and eliminate duplicates in case we've parsed twice.
3362+
auto newEnd = std::unique(
3363+
IfConfigRanges.Ranges.begin(), IfConfigRanges.Ranges.end(),
3364+
[&](const IfConfigRangeInfo &lhs, const IfConfigRangeInfo &rhs) {
3365+
if (lhs.getWholeRange() != rhs.getWholeRange())
3366+
return false;
3367+
3368+
assert(lhs == rhs && "Active ranges changed on a re-parse?");
3369+
return true;
3370+
});
3371+
IfConfigRanges.Ranges.erase(newEnd, IfConfigRanges.Ranges.end());
3372+
IfConfigRanges.IsSorted = true;
3373+
}
3374+
3375+
// First let's find the first #if that is after the outer start loc.
3376+
auto ranges = llvm::makeArrayRef(IfConfigRanges.Ranges);
3377+
auto lower = llvm::lower_bound(
3378+
ranges, outer.Start, [&](IfConfigRangeInfo range, SourceLoc loc) {
3379+
return SM.isBeforeInBuffer(range.getStartLoc(), loc);
3380+
});
3381+
if (lower == ranges.end() ||
3382+
SM.isBeforeInBuffer(outer.End, lower->getStartLoc())) {
3383+
return {};
3384+
}
3385+
// Next let's find the first #if that's after the outer end loc.
3386+
auto upper = llvm::upper_bound(
3387+
ranges, outer.End, [&](SourceLoc loc, IfConfigRangeInfo range) {
3388+
return SM.isBeforeInBuffer(loc, range.getStartLoc());
3389+
});
3390+
return llvm::makeArrayRef(lower, upper - lower);
3391+
}
3392+
33293393
void ModuleDecl::setPackageName(Identifier name) {
33303394
Package = PackageUnit::create(name, *this, getASTContext());
33313395
}

lib/Parse/ParseIfConfig.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ Result Parser::parseIfConfigRaw(
785785
bool isActive, IfConfigElementsRole role)>
786786
parseElements,
787787
llvm::function_ref<Result(SourceLoc endLoc, bool hadMissingEnd)> finish) {
788+
auto startLoc = Tok.getLoc();
788789
assert(Tok.is(tok::pound_if));
789790

790791
Parser::StructureMarkerRAII ParsingDecl(
@@ -824,6 +825,7 @@ Result Parser::parseIfConfigRaw(
824825

825826
bool foundActive = false;
826827
bool isVersionCondition = false;
828+
CharSourceRange activeBodyRange;
827829
while (1) {
828830
bool isElse = Tok.is(tok::pound_else);
829831
SourceLoc ClauseLoc = consumeToken();
@@ -881,6 +883,7 @@ Result Parser::parseIfConfigRaw(
881883
}
882884

883885
// Parse elements
886+
auto bodyStart = Lexer::getLocForEndOfToken(SourceMgr, PreviousLoc);
884887
llvm::SaveAndRestore<bool> S(InInactiveClauseEnvironment,
885888
InInactiveClauseEnvironment || !isActive);
886889
// Disable updating the interface hash inside inactive blocks.
@@ -899,6 +902,12 @@ Result Parser::parseIfConfigRaw(
899902
ClauseLoc, Condition, isActive, IfConfigElementsRole::Skipped);
900903
}
901904

905+
// Record the active body range for the SourceManager.
906+
if (shouldEvaluate && isActive) {
907+
assert(!activeBodyRange.isValid() && "Multiple active regions?");
908+
activeBodyRange = CharSourceRange(SourceMgr, bodyStart, Tok.getLoc());
909+
}
910+
902911
if (Tok.isNot(tok::pound_elseif, tok::pound_else))
903912
break;
904913

@@ -909,6 +918,12 @@ Result Parser::parseIfConfigRaw(
909918
SourceLoc EndLoc;
910919
bool HadMissingEnd = parseEndIfDirective(EndLoc);
911920

921+
// Record the #if ranges on the SourceManager.
922+
if (!HadMissingEnd && shouldEvaluate) {
923+
auto wholeRange = Lexer::getCharSourceRangeFromSourceRange(
924+
SourceMgr, SourceRange(startLoc, EndLoc));
925+
SF.recordIfConfigRangeInfo({wholeRange, activeBodyRange});
926+
}
912927
return finish(EndLoc, HadMissingEnd);
913928
}
914929

0 commit comments

Comments
 (0)