Skip to content

[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

Merged

Conversation

rintaro
Copy link
Member

@rintaro rintaro commented Aug 12, 2020

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:

  • Use getOriginalBodySourceRange() for AbstractFunctionBodyScope
  • Remember the replaced range in SourceManager
  • Translate source ranges for finding scopes when the lookup location is in the replaced range (findInnermostEnclosingScope())
  • Bypass source range checks when the child scope from the replaced range (widenSourceRangeForChildren() and verifyThatChildrenAreContainedWithin )
  • Clear ASTScope for each completion

Unrelated to fast completion:

  • Add generic params and where clause scope even with missing body
  • Be lenient with ASTScope / DeclContext mismatch in code completion mode

@rintaro rintaro force-pushed the ide-completion-astscope-rdar58939376-pt2 branch 2 times, most recently from 36fc543 to ca2f996 Compare August 12, 2020 21:33
@rintaro
Copy link
Member Author

rintaro commented Aug 12, 2020

@swift-ci Please smoke test

1 similar comment
@rintaro
Copy link
Member Author

rintaro commented Aug 12, 2020

@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.
Copy link
Contributor

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.

Copy link
Member Author

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.
Copy link
Contributor

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())
Copy link
Contributor

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?

Copy link
Member Author

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
                                         ^

Copy link
Member Author

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?

Copy link
Member Author

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

@slavapestov
Copy link
Contributor

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
@rintaro rintaro force-pushed the ide-completion-astscope-rdar58939376-pt2 branch from ca2f996 to bc20856 Compare August 12, 2020 23:37
@rintaro
Copy link
Member Author

rintaro commented Aug 13, 2020

@swift-ci Please smoke test

Copy link
Contributor

@davidungar davidungar left a 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!

return 0 >= ASTScopeImpl::compare(loc, scope->getSourceRangeOfScope(),
sourceMgr,
/*ensureDisjoint=*/false);
return !(*this)(scope, loc);
Copy link
Contributor

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?

Copy link
Member Author

@rintaro rintaro Aug 13, 2020

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 type ForwardIt can be dereferenced and then implicitly converted to Type1. The type Type2 must be such that an object of type T can be implicitly converted to Type2

So I rewrote this block to:

  auto *const *child = llvm::lower_bound(
      getChildren(), loc,
      [&sourceMgr](const ASTScopeImpl *scope, SourceLoc loc) {
         ...
      });

Any concern with this?

Copy link
Contributor

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?

Copy link
Member Author

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().
@rintaro
Copy link
Member Author

rintaro commented Aug 13, 2020

@swift-ci Please smoke test

1 similar comment
@rintaro
Copy link
Member Author

rintaro commented Aug 13, 2020

@swift-ci Please smoke test

Comment on lines +172 to +187
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Great! Thanks.

Copy link
Contributor

@davidungar davidungar left a comment

Choose a reason for hiding this comment

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

Great! Thanks.

@rintaro rintaro merged commit 21b91cd into swiftlang:master Aug 13, 2020
@rintaro rintaro deleted the ide-completion-astscope-rdar58939376-pt2 branch August 13, 2020 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants