Skip to content

[ASTScope] Re-enable checkSourceRangeBeforeAddingChild #80339

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
merged 7 commits into from
Mar 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion include/swift/AST/ASTScope.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ class ASTScopeImpl : public ASTAllocated<ASTScopeImpl> {
NullablePtr<Stmt> getStmtIfAny() const;
NullablePtr<Expr> getExprIfAny() const;

/// Whether this scope is for a decl attribute.
bool isDeclAttribute() const;

#pragma mark - debugging and printing

public:
Expand All @@ -264,7 +267,9 @@ class ASTScopeImpl : public ASTAllocated<ASTScopeImpl> {
void dumpOneScopeMapLocation(std::pair<unsigned, unsigned> lineColumn);

private:
llvm::raw_ostream &verificationError() const;
[[noreturn]]
void abortWithVerificationError(
llvm::function_ref<void(llvm::raw_ostream &)> messageFn) const;

#pragma mark - Scope tree creation
public:
Expand Down
14 changes: 14 additions & 0 deletions lib/AST/ASTScope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,20 @@ NullablePtr<Expr> ASTScopeImpl::getExprIfAny() const {
}
}

bool ASTScopeImpl::isDeclAttribute() const {
switch (getKind()) {
#define DECL_ATTRIBUTE_SCOPE_NODE(Name) \
case ScopeKind::Name: return true;
#define SCOPE_NODE(Name)
#include "swift/AST/ASTScopeNodes.def"

#define DECL_ATTRIBUTE_SCOPE_NODE(Name)
#define SCOPE_NODE(Name) case ScopeKind::Name:
#include "swift/AST/ASTScopeNodes.def"
return false;
}
}

SourceManager &ASTScopeImpl::getSourceManager() const {
return getASTContext().SourceMgr;
}
Expand Down
21 changes: 16 additions & 5 deletions lib/AST/ASTScopeCreation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,12 @@ ASTScopeImpl *ScopeCreator::addToScopeTreeAndReturnInsertionPoint(
void ScopeCreator::addChildrenForParsedAccessors(
AbstractStorageDecl *asd, ASTScopeImpl *parent) {
asd->visitParsedAccessors([&](AccessorDecl *ad) {
// Ignore accessors added by macro expansions.
// TODO: This ought to be the default behavior of `visitParsedAccessors`,
// we ought to have a different entrypoint for clients that care about
// the semantic set of "explicit" accessors.
if (ad->isInMacroExpansionInContext())
return;
assert(asd == ad->getStorage());
this->addToScopeTree(ad, parent);
});
Expand Down Expand Up @@ -666,11 +672,16 @@ ScopeCreator::addPatternBindingToScopeTree(PatternBindingDecl *patternBinding,
if (auto *var = patternBinding->getSingleVar())
addChildrenForKnownAttributes(var, parentScope);

// Check to see if we have a local binding. Note we need to exclude bindings
// in `@abi` attributes here since they may be in a local DeclContext, but
// their scope shouldn't extend past the attribute.
bool isLocalBinding = false;
for (auto i : range(patternBinding->getNumPatternEntries())) {
if (auto *varDecl = patternBinding->getAnchoringVarDecl(i)) {
isLocalBinding = varDecl->getDeclContext()->isLocalContext();
break;
if (!isa<ABIAttributeScope>(parentScope)) {
for (auto i : range(patternBinding->getNumPatternEntries())) {
if (auto *varDecl = patternBinding->getAnchoringVarDecl(i)) {
isLocalBinding = varDecl->getDeclContext()->isLocalContext();
break;
}
}
}

Expand Down Expand Up @@ -703,7 +714,7 @@ void ASTScopeImpl::addChild(ASTScopeImpl *child, ASTContext &ctx) {
child->parentAndWasExpanded.setPointer(this);

#ifndef NDEBUG
// checkSourceRangeBeforeAddingChild(child, ctx);
checkSourceRangeBeforeAddingChild(child, ctx);
#endif

// If this is the first time we've added children, notify the ASTContext
Expand Down
14 changes: 11 additions & 3 deletions lib/AST/ASTScopePrinting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,17 @@ void ASTScopeImpl::dumpOneScopeMapLocation(
}
}

llvm::raw_ostream &ASTScopeImpl::verificationError() const {
return llvm::errs() << "ASTScopeImpl verification error in source file '"
<< getSourceFile()->getFilename() << "': ";
void ASTScopeImpl::abortWithVerificationError(
llvm::function_ref<void(llvm::raw_ostream &)> messageFn) const {
llvm::SmallString<0> errorStr;
llvm::raw_svector_ostream out(errorStr);

out << "ASTScopeImpl verification error in source file '"
<< getSourceFile()->getFilename() << "':\n";
messageFn(out);

llvm::PrettyStackTraceString trace(errorStr.c_str());
abort();
}

#pragma mark printing
Expand Down
53 changes: 27 additions & 26 deletions lib/AST/ASTScopeSourceRange.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,15 @@ using namespace ast_scope;

static SourceLoc getLocAfterExtendedNominal(const ExtensionDecl *);

/// Retrieve the character-based source range for the given source range.
static SourceRange getCharSourceRange(
SourceManager &sourceMgr, SourceRange range
){
range.End = Lexer::getLocForEndOfToken(sourceMgr, range.End);
return range;
}

void ASTScopeImpl::checkSourceRangeBeforeAddingChild(ASTScopeImpl *child,
const ASTContext &ctx) const {
// Ignore attributes on extensions, currently they exist outside of the
// extension's source range due to the way we've setup the scope for
// extension binding.
// FIXME: We ought to fix the source range for extension scopes.
if (isa<ExtensionScope>(this) && child->isDeclAttribute())
return;

// Ignore debugger bindings - they're a special mix of user code and implicit
// wrapper code that is too difficult to check for consistency.
if (auto d = getDeclIfAny().getPtrOrNull())
Expand All @@ -60,14 +59,14 @@ void ASTScopeImpl::checkSourceRangeBeforeAddingChild(ASTScopeImpl *child,

auto range = getCharSourceRangeOfScope(sourceMgr);

std::function<bool(SourceRange)> containedInParent;
containedInParent = [&](SourceRange childCharRange) {
auto containedInParent = [&](SourceRange childCharRange) {
// HACK: For code completion. Handle replaced range.
// Note that the replaced SourceRanges here are already disguised
// CharSourceRanges, we don't need to adjust them. We use `rangeContains`
// since we're only interested in comparing within a single buffer.
for (const auto &pair : sourceMgr.getReplacedRanges()) {
auto originalRange = getCharSourceRange(sourceMgr, pair.first);
auto newRange = getCharSourceRange(sourceMgr, pair.second);
if (sourceMgr.encloses(range, originalRange) &&
sourceMgr.encloses(newRange, childCharRange))
if (sourceMgr.rangeContains(range, pair.first) &&
sourceMgr.rangeContains(pair.second, childCharRange))
return true;
}

Expand All @@ -77,11 +76,12 @@ void ASTScopeImpl::checkSourceRangeBeforeAddingChild(ASTScopeImpl *child,
auto childCharRange = child->getCharSourceRangeOfScope(sourceMgr);

if (!containedInParent(childCharRange)) {
auto &out = verificationError() << "child not contained in its parent:\n";
child->print(out);
out << "\n***Parent node***\n";
this->print(out);
abort();
abortWithVerificationError([&](llvm::raw_ostream &out) {
out << "child not contained in its parent:\n";
child->print(out);
out << "\n***Parent node***\n";
this->print(out);
});
}

if (!storedChildren.empty()) {
Expand All @@ -90,13 +90,14 @@ void ASTScopeImpl::checkSourceRangeBeforeAddingChild(ASTScopeImpl *child,
sourceMgr).End;

if (!sourceMgr.isAtOrBefore(endOfPreviousChild, childCharRange.Start)) {
auto &out = verificationError() << "child overlaps previous child:\n";
child->print(out);
out << "\n***Previous child\n";
previousChild->print(out);
out << "\n***Parent node***\n";
this->print(out);
abort();
abortWithVerificationError([&](llvm::raw_ostream &out) {
out << "child overlaps previous child:\n";
child->print(out);
out << "\n***Previous child\n";
previousChild->print(out);
out << "\n***Parent node***\n";
this->print(out);
});
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1007,11 +1007,21 @@ SourceRange Decl::getSourceRangeIncludingAttrs() const {
}

bool Decl::isInMacroExpansionInContext() const {
auto *dc = getDeclContext();
auto parentFile = dc->getParentSourceFile();
auto *mod = getModuleContext();
auto *file = mod->getSourceFileContainingLocation(getStartLoc());

auto parentFile = [&]() {
// For accessors, the storage decl is the more accurate thing to check
// since the entire property/subscript could be macro-generated, in which
// case the accessor shouldn't be considered "added by macro expansion".
if (auto *accessor = dyn_cast<AccessorDecl>(this)) {
auto storageLoc = accessor->getStorage()->getLoc();
if (storageLoc.isValid())
return mod->getSourceFileContainingLocation(storageLoc);
}
Comment on lines +1014 to +1021
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could split this logic out into a separate method specific to AccessorDecl, but it seems to me that this is the behavior that clients would expect for isInMacroExpansionInContext, even if it's not strictly based on the DeclContext

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I agree that what you have here is the intended behavior of isInMacroExpansionInContext

return getDeclContext()->getParentSourceFile();
}();

// Decls in macro expansions always have a source file. The source
// file can be null if the decl is implicit or has an invalid
// source location.
Expand Down
4 changes: 2 additions & 2 deletions lib/Basic/SourceLoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -836,11 +836,11 @@ static bool isBeforeInSource(
SourceLoc firstLocInLCA = firstMismatch == firstAncestors.end()
? firstLoc
: sourceMgr.getGeneratedSourceInfo(*firstMismatch)
->originalSourceRange.getEnd();
->originalSourceRange.getStart();
SourceLoc secondLocInLCA = secondMismatch == secondAncestors.end()
? secondLoc
: sourceMgr.getGeneratedSourceInfo(*secondMismatch)
->originalSourceRange.getEnd();
->originalSourceRange.getStart();
Comment on lines +839 to +843
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hborla This effectively reverts 8c9eeb2, AFAIK that shouldn't be necessary with 60da121, let me know if there's a case I'm missing though

return sourceMgr.isBeforeInBuffer(firstLocInLCA, secondLocInLCA) ||
(allowEqual && firstLocInLCA == secondLocInLCA);
}
Expand Down
6 changes: 6 additions & 0 deletions lib/Sema/TypeCheckMacros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,12 @@ static CharSourceRange getExpansionInsertionRange(MacroRole role,
closure->getEndLoc()));
}

// If the function has a body, that's what's being replaced.
auto *AFD = cast<AbstractFunctionDecl>(target.get<Decl *>());
if (auto range = AFD->getBodySourceRange())
return Lexer::getCharSourceRangeFromSourceRange(sourceMgr, range);

// Otherwise we have no body, just use the end of the decl.
SourceLoc afterDeclLoc =
Lexer::getLocForEndOfToken(sourceMgr, target.getEndLoc());
return CharSourceRange(afterDeclLoc, 0);
Expand Down