Skip to content

[CodeCompletion] Parse #if block containing CC token as active #33475

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 14, 2020

Treat the region containing the completion location as "active" during parsing.

  1. When #if is found, see if the completion location is between #if and #endif (using backtracking.) If not, parse the entire #if ... #endif normally
  2. For each region, see if the completion location is in the region (using backtracking.) If yes, parse it as "active" block, otherwise treat it "inactive"

This is not so efficient because the Lexer runs up to 3 times for each IfConfig region. But Lexer is pretty fast, and doesn't consume much memory. So it shouldn't be a problem.

This also guarantees that the completion never happens inside "inactive" blocks. Since Sema usually don't expect type checking inside inactive blocks, this change might improve the overall stability of code-completion inside #if ... #endif.

The second commit is removing includeInactiveIfConfigClauses from ASTScope. This flag was prepared for code-completion, but had never been enabled. Now that, we guarantee that name lookup never happen inside inactive blocks, we don't need this flag anymore. cc: @davidungar

rdar://problem/67027408

@rintaro
Copy link
Member Author

rintaro commented Aug 14, 2020

@swift-ci Please smoke test

@rintaro rintaro force-pushed the ide-completion-inactiveifconfig-rdar67027408 branch from b74bc58 to 4192e18 Compare August 14, 2020 04:48
Comment on lines -699 to +700
while (Tok.isNot(T1, T2, tok::eof, tok::pound_endif, tok::code_complete))

while (Tok.isNot(T1, T2, tok::eof, tok::pound_endif, tok::pound_else,
tok::pound_elseif))
Copy link
Member Author

@rintaro rintaro Aug 14, 2020

Choose a reason for hiding this comment

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

tok::code_complete was here to prevent excessive skipping beyond the completion point. But we sometimes want to skip code_complete token, for example canParse*** kind of functions. This doesn't cause any visible regression because skipUntilDeclRBrace() family skipping functions still stop at tok::code_complete.

As for tok::pound_else and tok::pound_elseif, they should be treated as the same as tok::pound_endif in terms of "skipping" tokens.

@rintaro rintaro force-pushed the ide-completion-inactiveifconfig-rdar67027408 branch from 4192e18 to b6051b6 Compare August 14, 2020 04:51
@rintaro rintaro requested a review from benlangmuir August 14, 2020 04:52
@rintaro
Copy link
Member Author

rintaro commented Aug 14, 2020

@swift-ci Please smoke test

Copy link
Contributor

@benlangmuir benlangmuir left a comment

Choose a reason for hiding this comment

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

This approach only applies to the #if immediately surrounding the completion, so it can lead to non-sensical completions

#if FOO
let x: Foo
#else
let x: NotFoo
#endif 
...
#if FOO
x.<here>
#endif

If we moved #if condition evaluation after parsing we could look at the #if condition for the completion location and choose a config that is consistent with that for all other #ifs. If there are || branches we might choose something invalid, but seems overall reasonable. What do you think?


What impact does this change have on AST reuse if the #if is outside of a function body? Will it still work? Will it cause the interface hash to change if you complete in the inactive block?


This is not so efficient because the Lexer runs up to 3 times for each IfConfig region

What if we do a single extra pass and track the #else locations along the way so we can know directly which branch contains the completion?

// See if this '#if ... #endif' directive contains code completion token.
bool hasCCToken = false;
if (SourceMgr.hasCodeCompletionBuffer() &&
SourceMgr.getCodeCompletionBufferID() == L->getBufferID()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We could also check that this is not after the completion loc already.

This flag has never been enabled.
Now that, Parser treats IfConfig block containing CC token as "active",
so code completion doesn't lookup from inactive blocks.
@rintaro rintaro force-pushed the ide-completion-inactiveifconfig-rdar67027408 branch from 0c98a40 to 7198b3b Compare August 14, 2020 17:34
@rintaro
Copy link
Member Author

rintaro commented Aug 14, 2020

If we moved #if condition evaluation after parsing we could look at the #if condition for the completion location and choose a config that is consistent with that for all other #ifs.

It is a long standing desire to separate #if conditions evaluation from parsing. We want to do it eventually. But, for code-completion inside inactive blocks, I'm not sure we'd gain enough benefit for the effort.
For example:

#if os(macOS)
import AppKit
#else
import UIKit
#endif 

#if os(macOS)
let color = NSColor.black
#else
let color = UIColor.#HERE#
#endif

This fails if the user is working on macOS target because macOS SDK doesn't have UIKit anyway.

@rintaro
Copy link
Member Author

rintaro commented Aug 14, 2020

What impact does this change have on AST reuse if the #if is outside of a function body? Will it still work? Will it cause the interface hash to change if you complete in the inactive block?

My original patch had a bug where interface hash continued to be recorded while backtracking. After fixing that, it works as expected. Switching between active and inactive blocks changes the interface hash.

@rintaro
Copy link
Member Author

rintaro commented Aug 14, 2020

What if we do a single extra pass and track the #else locations along the way so we can know directly which branch contains the completion?

Great idea! Implemented.

// If this directive contains code completion, 'isActive' is determined
// solely by which block has the completion token.
!hasCCToken;
// If this directive contains code completion locaiton, 'isActive' is
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: locaiton -> location

Copy link
Member Author

Choose a reason for hiding this comment

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

🤦

@rintaro rintaro force-pushed the ide-completion-inactiveifconfig-rdar67027408 branch from 8b3a27d to 6cfdaf6 Compare August 14, 2020 18:15
@rintaro
Copy link
Member Author

rintaro commented Aug 14, 2020

@swift-ci Please smoke test

@rintaro
Copy link
Member Author

rintaro commented Aug 14, 2020

@swift-ci Please smoke test macOS

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.

2 participants