Skip to content
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

fix(ide-completion): fix handling of for in impl T for A in function body #18005

Merged
merged 1 commit into from
Aug 30, 2024

Conversation

rami3l
Copy link
Member

@rami3l rami3l commented Aug 30, 2024

Closes #17787.

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 30, 2024
@lnicola

This comment was marked as outdated.

@rami3l
Copy link
Member Author

rami3l commented Aug 30, 2024

The couple of failed tests I checked seem fine, they need a baseline update.

@lnicola Thanks! The fix per se looks good to me, but I wonder if this is in line with other stuff, such as:

#[test]
fn completes_flyimport_with_doc_alias_in_another_mod() {
check(
r#"
mod foo {
#[doc(alias = "Qux")]
pub struct Bar();
}
fn here_we_go() {
let foo = Bar$0
}
"#,
expect![[r#"
fn here_we_go() fn()
md foo
st Bar (alias Qux) (use foo::Bar) Bar
bt u32 u32
kw crate::
kw false
kw for
kw if
kw if let
kw loop
kw match
kw return
kw self::
kw true
kw unsafe
kw while
kw while let
"#]],
);
}

Do we really need all those suggestions? This PR eliminates for which seems reasonable to me, but what about other stuff like loop and match? Maybe in_block_expr is not the correct predicate here?

@rami3l
Copy link
Member Author

rami3l commented Aug 30, 2024

@lnicola The key issue here seems to be that impl T for A is local to the function, otherwise I cannot reproduce #17787. I'll try finding a smarter way of doing things, and adding a test case if possible.

@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

We incorrectly land in a PathKind::Expr here in completion analysis likely due to parser recovery

@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

Given

fn foo() {
    struct X;
    impl X fo { }
}

we get a

(there is a show syntax tree command in vscode to get this view)
as youcan see, we parse the fo outside of the impl but within the block expression of the function, so we parse it as an expression. From a recovery perspective this does make sense, so we should probably fix that up in the completion analysis? Not sure,
In the non local item position we g et

so there its a macro call. We should already be handling that case (I get a for completion there), so might be good to check how we handle that in completion analysis

@rami3l rami3l force-pushed the fix/for-completion-in-impl branch from 0d33a77 to d4fdb5a Compare August 30, 2024 11:47
@rami3l rami3l changed the title fix(ide-completion): don't trigger for loop outside of block expr fix(ide-completion): fix handling of for in impl T for A in function body Aug 30, 2024
@rami3l
Copy link
Member Author

rami3l commented Aug 30, 2024

@Veykril Thanks for your pointers! I think I've found a way to be less disruptive, let's see how the CI goes.

Update: Checking impl_.is_some() is still not right. A counter-example would be:

fn foo() {
    struct X;
    impl X { fn bar() { $0 } }
}

@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

The proper fix will require changes in crates\ide-completion\src\context\analysis.rs as we land in the wrong branch somewhere (due to not accounting for parser recovery)

@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

pub(crate) fn complete_for_and_where(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
keyword_item: &ast::Item,
) {
let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
match keyword_item {
Item::Impl(it) => {
if it.for_token().is_none() && it.trait_().is_none() && it.self_ty().is_some() {
add_keyword("for", "for $0");
}
add_keyword("where", "where $0");
}
Item::Enum(_)
| Item::Fn(_)
| Item::Struct(_)
| Item::Trait(_)
| Item::TypeAlias(_)
| Item::Union(_) => {
add_keyword("where", "where $0");
}
_ => (),
}
}
is where we should be landing in this snippet instead of the expression completion

@rami3l
Copy link
Member Author

rami3l commented Aug 30, 2024

pub(crate) fn complete_for_and_where(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
keyword_item: &ast::Item,
) {
let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
match keyword_item {
Item::Impl(it) => {
if it.for_token().is_none() && it.trait_().is_none() && it.self_ty().is_some() {
add_keyword("for", "for $0");
}
add_keyword("where", "where $0");
}
Item::Enum(_)
| Item::Fn(_)
| Item::Struct(_)
| Item::Trait(_)
| Item::TypeAlias(_)
| Item::Union(_) => {
add_keyword("where", "where $0");
}
_ => (),
}
}

is where we should be landing in this snippet instead of the expression completion

@Veykril I'm aware of this, it's just that the only possible context of this call is:

NameRefKind::Keyword(item) => {
keyword::complete_for_and_where(acc, ctx, item);
}

Clearly it's not the same as in a fn, where we mostly expect statements (including items, macro calls and expression statements).

In this case, given how the function is written, I guess PathKind::Expr stands for exactly that: something expression-like. Most of the time you'd want an expression.

Let me see what I can do to improve

...

@rami3l rami3l force-pushed the fix/for-completion-in-impl branch from d4fdb5a to e627f38 Compare August 30, 2024 12:15
@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

If the preceding token to the cursor is an impl Foo then the only things thats may follow that Foo is a for token, a where token or a curly brace, not an expression. So the completion analysis should compute a NameRefKind::Keyword here, not a NameRefKind::Path

@rami3l
Copy link
Member Author

rami3l commented Aug 30, 2024

If the preceding token to the cursor is an impl Foo then the only things thats may follow that Foo is a for token, a where token or a curly brace, not an expression. So the completion analysis should compute a NameRefKind::Keyword here, not a NameRefKind::Path

I see. I believe it works outside of functions because you special-cased it:

ast::MacroCall(it) => {
// A macro call in this position is usually a result of parsing recovery, so check that
if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
return Some(make_res(NameRefKind::Keyword(kind)));
}

... maybe I can apply a similar hack to this case.

@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

Yes thats the special case handling it outside of bodies, would be neat if that can be re-used though its probably a bit more tricky

@rami3l rami3l force-pushed the fix/for-completion-in-impl branch from e627f38 to 3d13c7f Compare August 30, 2024 12:56
@rami3l
Copy link
Member Author

rami3l commented Aug 30, 2024

Yes thats the special case handling it outside of bodies, would be neat if that can be re-used though its probably a bit more tricky

@Veykril So by adding the same hack to records in 3d13c7f, I was able to address the original issue. However, I'm still more interested in the fn foo() { impl X for $0 } case.

@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

What exaclty is with fn foo() { impl X for $0 }?

@rami3l
Copy link
Member Author

rami3l commented Aug 30, 2024

What exaclty is with fn foo() { impl X for $0 }?

The inbetween_body_and_decl_check() hack on records has worked with fn foo() { impl X $0 {} } because $0 {} was resolved to a record, according to your first example in #18005 (comment). However I'm not sure how to handle the case without {}.

fn foo() { impl X fo } gives something like:

@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

Isn't that this case? https://github.com/rust-lang/rust-analyzer/pull/18005/files#diff-9cef2bb04b7b1bc7d74e0d5822c991876dec30dce47004692a99369f18651442R166-R180
I'm sorry I'm confused, fn foo() { impl X for $0 {} } already works fine today for me, your change ought to make fn foo() { impl X fo$0 {} } / fn foo() { impl X $0 {} } work from what I can tell

@rami3l rami3l force-pushed the fix/for-completion-in-impl branch from 3d13c7f to c74a829 Compare August 30, 2024 13:27
@rami3l
Copy link
Member Author

rami3l commented Aug 30, 2024

I'm sorry I'm confused, fn foo() { impl X for $0 {} } already works fine today for me, your change ought to make fn foo() { impl X fo$0 {} } / fn foo() { impl X $0 {} } work from what I can tell

@Veykril No worries. I believe now I've fixed both of them.

@rami3l rami3l force-pushed the fix/for-completion-in-impl branch from c74a829 to 0b28126 Compare August 30, 2024 13:40
@rami3l rami3l marked this pull request as ready for review August 30, 2024 13:40
@Veykril
Copy link
Member

Veykril commented Aug 30, 2024

Thanks!
@bors r+

@bors
Copy link
Contributor

bors commented Aug 30, 2024

📌 Commit 0b28126 has been approved by Veykril

It is now in the queue for this repository.

@bors
Copy link
Contributor

bors commented Aug 30, 2024

⌛ Testing commit 0b28126 with merge 2890b10...

@bors
Copy link
Contributor

bors commented Aug 30, 2024

☀️ Test successful - checks-actions
Approved by: Veykril
Pushing 2890b10 to master...

@bors bors merged commit 2890b10 into rust-lang:master Aug 30, 2024
11 checks passed
@rami3l rami3l deleted the fix/for-completion-in-impl branch August 30, 2024 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

in function body use impl for, expand to for loop
5 participants