-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[CodeCompletion] Enable ASTScope in code completion #33433
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
[CodeCompletion] Enable ASTScope in code completion #33433
Conversation
36fc543
to
ca2f996
Compare
@swift-ci Please smoke test |
1 similar comment
@swift-ci Please smoke test |
@@ -82,6 +82,14 @@ const ASTScopeImpl *ASTScopeImpl::findStartingScopeForLookup( | |||
// Someday, just use the assertion below. For now, print out lots of info for | |||
// debugging. | |||
if (!startingScope) { | |||
|
|||
// Be lenient in code completion mode. There are cases where the decl | |||
// context doesn't match with the ASTScope. e.g. dangling attributes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a FIXME and file a radar for me or Dave? The ASTScope should be a 'single source of truth', even for invalid code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed rdar://problem/66945121
@@ -67,6 +67,14 @@ ASTScopeImpl::widenSourceRangeForChildren(const SourceRange range, | |||
if (range.isInvalid()) | |||
return childRange; | |||
auto r = range; | |||
|
|||
// HACK: For code completion. If the range of the child is from another | |||
// source buffer, don't widen using that range. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we try to clean this up as well?
|
||
// Prevent circular request bugs caused by illegal input and | ||
// doing lookups that getExtendedNominal in the midst of getExtendedNominal. | ||
if (scope->shouldHaveABody() && !scope->doesDeclHaveABody()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this happen in non-code completion cases too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, yes. For example, for:
extension Collection where Element == Int
current master emits
test.swift:2:42: error: expected '{' in extension
extension Collection where Element == Int
^
test.swift:2:28: error: cannot find type 'Element' in scope
extension Collection where Element == Int
^~~~~~~
test.swift:2:36: error: generic signature requires types '<<error type>>' and 'Int' to be the same
extension Collection where Element == Int
^
With this change, it's just:
test.swift:2:42: error: expected '{' in extension
extension Collection where Element == Int
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which file can I add a test case for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to test/decl/ext/extensions.swift
This is great, even with the hacks, which I think we can try to sort out later. The only remaining usage of non-ASTScope lookup is now in lldb. |
* Re-create `ASTScope` for each completion * Add generic params and where clause scope even without missing body * Use `getOriginalBodySourceRange()` for `AbstractFunctionBodyScope` * Source range translations for replaced ranges when finding scopes * Bypass source range checks when the completion happens in the replaced range * Be lenient with ASTScope / DeclContext mismatch in code completion
rdar://problem/66943328
ca2f996
to
bc20856
Compare
@swift-ci Please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, looks pretty good. Just one comment where I could use some reassurance, because the code seems to assume that !(a<b) == b<a . Thanks for doing this!
lib/AST/ASTScopeLookup.cpp
Outdated
return 0 >= ASTScopeImpl::compare(loc, scope->getSourceRangeOfScope(), | ||
sourceMgr, | ||
/*ensureDisjoint=*/false); | ||
return !(*this)(scope, loc); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please double-check this? (Or maybe just reassure me) What about the == case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for notifying! I think your concern is legitimate (the comparison function should not return true
for ==
case).
But, it turned out that this function is not called. According to https://en.cppreference.com/w/cpp/algorithm/lower_bound , comparison happens only by comp(element, value)
, but not comp(value, element)
.
The signature of the predicate function should be equivalent to the following:
bool pred(const Type1 &a, const Type2 &b);
The type
Type1
must be such that an object of typeForwardIt
can be dereferenced and then implicitly converted toType1
. The typeType2
must be such that an object of typeT
can be implicitly converted toType2
So I rewrote this block to:
auto *const *child = llvm::lower_bound(
getChildren(), loc,
[&sourceMgr](const ASTScopeImpl *scope, SourceLoc loc) {
...
});
Any concern with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for addressing it. I worry that a future maintainer, (or even myself) will get confused. If the function on line 183 is not called, do we still need to supply it? If so, can we just put an llvm unreachable in it? Otherwise, how about a comment around line 184 explaining away the potential confusion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, since I removed operator()(SourceLoc loc, const ASTScopeImpl *scope)
and rewrote CompareLocs
with a lambda expression, I believe there's no unreachable code anymore.
The comparision happens only by 'comp(element, value)' not 'comp(value, element)'. We only need `operator()(const ASTScopeImpl *, SourceLoc)`. Use llvm::lower_bound() instead of std::lower_bound().
@swift-ci Please smoke test |
1 similar comment
@swift-ci Please smoke test |
auto *const *child = llvm::lower_bound( | ||
getChildren(), loc, | ||
[&sourceMgr](const ASTScopeImpl *scope, SourceLoc loc) { | ||
ASTScopeAssert(scope->checkSourceRangeOfThisASTNode(), "Bad range."); | ||
auto rangeOfScope = scope->getSourceRangeOfScope(); | ||
loc = translateLocForReplacedRange(sourceMgr, rangeOfScope, loc); | ||
return -1 == ASTScopeImpl::compare(rangeOfScope, loc, sourceMgr, | ||
/*ensureDisjoint=*/false); | ||
}); | ||
|
||
if (child != getChildren().end()) { | ||
auto rangeOfScope = (*child)->getSourceRangeOfScope(); | ||
loc = translateLocForReplacedRange(sourceMgr, rangeOfScope, loc); | ||
if (sourceMgr.rangeContainsTokenLoc(rangeOfScope, loc)) | ||
return *child; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! Thanks.
Enable ASTScope lookup (by default) in code completion.
rdar://problem/58939376
For fast-completion where the body of
AbstractFunctionDecl
might be replaced with another body parsed from another source buffer:getOriginalBodySourceRange()
forAbstractFunctionBodyScope
SourceManager
findInnermostEnclosingScope()
)widenSourceRangeForChildren()
andverifyThatChildrenAreContainedWithin
)ASTScope
for each completionUnrelated to fast completion:
ASTScope
/DeclContext
mismatch in code completion mode