From f3ae5e56fbf35e7b47a071b28432f72b669f444a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 10 Oct 2022 15:45:24 +0200 Subject: [PATCH] Refactor completions expansion --- crates/ide-completion/src/context.rs | 43 +- crates/ide-completion/src/context/analysis.rs | 1939 +++++++++-------- 2 files changed, 1011 insertions(+), 971 deletions(-) diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index a5e854b74df9..5ac460fc6eb9 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -23,7 +23,10 @@ use syntax::{ }; use text_edit::Indel; -use crate::CompletionConfig; +use crate::{ + context::analysis::{expand_and_analyze, AnalysisResult}, + CompletionConfig, +}; const COMPLETION_MARKER: &str = "intellijRulezz"; @@ -561,15 +564,27 @@ impl<'a> CompletionContext<'a> { let edit = Indel::insert(offset, COMPLETION_MARKER.to_string()); parse.reparse(&edit).tree() }; - let fake_ident_token = - file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?; + // always pick the token to the immediate left of the cursor, as that is what we are actually + // completing on let original_token = original_file.syntax().token_at_offset(offset).left_biased()?; - let token = sema.descend_into_macros_single(original_token.clone()); + + let AnalysisResult { + analysis, + expected: (expected_type, expected_name), + qualifier_ctx, + token, + offset, + } = expand_and_analyze( + &sema, + original_file.syntax().clone(), + file_with_fake_ident.syntax().clone(), + offset, + &original_token, + )?; // adjust for macro input, this still fails if there is no token written yet - let scope_offset = if original_token == token { offset } else { token.text_range().end() }; - let scope = sema.scope_at_offset(&token.parent()?, scope_offset)?; + let scope = sema.scope_at_offset(&token.parent()?, offset)?; let krate = scope.krate(); let module = scope.module(); @@ -583,7 +598,7 @@ impl<'a> CompletionContext<'a> { let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count(); - let mut ctx = CompletionContext { + let ctx = CompletionContext { sema, scope, db, @@ -593,19 +608,13 @@ impl<'a> CompletionContext<'a> { token, krate, module, - expected_name: None, - expected_type: None, - qualifier_ctx: Default::default(), + expected_name, + expected_type, + qualifier_ctx, locals, depth_from_crate_root, }; - let ident_ctx = ctx.expand_and_analyze( - original_file.syntax().clone(), - file_with_fake_ident.syntax().clone(), - offset, - fake_ident_token, - )?; - Some((ctx, ident_ctx)) + Some((ctx, analysis)) } } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 01dd9a234f55..04111ec7efaa 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -11,1063 +11,1094 @@ use syntax::{ }; use crate::context::{ - AttrCtx, CompletionAnalysis, CompletionContext, DotAccess, DotAccessKind, ExprCtx, - ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, NameRefContext, - NameRefKind, ParamContext, ParamKind, PathCompletionCtx, PathKind, PatternContext, - PatternRefutability, Qualified, QualifierCtx, TypeAscriptionTarget, TypeLocation, - COMPLETION_MARKER, + AttrCtx, CompletionAnalysis, DotAccess, DotAccessKind, ExprCtx, ItemListKind, LifetimeContext, + LifetimeKind, NameContext, NameKind, NameRefContext, NameRefKind, ParamContext, ParamKind, + PathCompletionCtx, PathKind, PatternContext, PatternRefutability, Qualified, QualifierCtx, + TypeAscriptionTarget, TypeLocation, COMPLETION_MARKER, }; -impl<'a> CompletionContext<'a> { - /// Expand attributes and macro calls at the current cursor position for both the original file - /// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original - /// and speculative states stay in sync. - pub(super) fn expand_and_analyze( - &mut self, - mut original_file: SyntaxNode, - mut speculative_file: SyntaxNode, - mut offset: TextSize, - mut fake_ident_token: SyntaxToken, - ) -> Option { - let _p = profile::span("CompletionContext::expand_and_fill"); - let mut derive_ctx = None; - - 'expansion: loop { - let parent_item = - |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast); - let ancestor_items = iter::successors( - Option::zip( - find_node_at_offset::(&original_file, offset), - find_node_at_offset::(&speculative_file, offset), +struct ExpansionResult { + original_file: SyntaxNode, + speculative_file: SyntaxNode, + offset: TextSize, + fake_ident_token: SyntaxToken, + derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>, +} + +pub(super) struct AnalysisResult { + pub(super) analysis: CompletionAnalysis, + pub(super) expected: (Option, Option), + pub(super) qualifier_ctx: QualifierCtx, + pub(super) token: SyntaxToken, + pub(super) offset: TextSize, +} + +pub(super) fn expand_and_analyze( + sema: &Semantics<'_, RootDatabase>, + original_file: SyntaxNode, + speculative_file: SyntaxNode, + offset: TextSize, + original_token: &SyntaxToken, +) -> Option { + // as we insert after the offset, right biased will *always* pick the identifier no matter + // if there is an ident already typed or not + let fake_ident_token = speculative_file.token_at_offset(offset).right_biased()?; + // the relative offset between the cursor and the *identifier* token we are completing on + let relative_offset = offset - fake_ident_token.text_range().start(); + // make the offset point to the start of the original token, as that is what the + // intermediate offsets calculated in expansion always points to + let offset = offset - relative_offset; + let expansion = expand(sema, original_file, speculative_file, offset, fake_ident_token); + // add the relative offset back, so that left_biased finds the proper token + let offset = expansion.offset + relative_offset; + let token = expansion.original_file.token_at_offset(offset).left_biased()?; + + analyze(sema, expansion, original_token, &token).map(|(analysis, expected, qualifier_ctx)| { + AnalysisResult { analysis, expected, qualifier_ctx, token, offset } + }) +} + +/// Expand attributes and macro calls at the current cursor position for both the original file +/// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original +/// and speculative states stay in sync. +fn expand( + sema: &Semantics<'_, RootDatabase>, + mut original_file: SyntaxNode, + mut speculative_file: SyntaxNode, + mut offset: TextSize, + mut fake_ident_token: SyntaxToken, +) -> ExpansionResult { + let _p = profile::span("CompletionContext::expand"); + let mut derive_ctx = None; + + 'expansion: loop { + let parent_item = + |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast); + let ancestor_items = iter::successors( + Option::zip( + find_node_at_offset::(&original_file, offset), + find_node_at_offset::(&speculative_file, offset), + ), + |(a, b)| parent_item(a).zip(parent_item(b)), + ); + + // first try to expand attributes as these are always the outermost macro calls + 'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items { + match ( + sema.expand_attr_macro(&actual_item), + sema.speculative_expand_attr_macro( + &actual_item, + &item_with_fake_ident, + fake_ident_token.clone(), ), - |(a, b)| parent_item(a).zip(parent_item(b)), - ); - - // first try to expand attributes as these are always the outermost macro calls - 'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items { - match ( - self.sema.expand_attr_macro(&actual_item), - self.sema.speculative_expand_attr_macro( - &actual_item, - &item_with_fake_ident, - fake_ident_token.clone(), - ), - ) { - // maybe parent items have attributes, so continue walking the ancestors - (None, None) => continue 'ancestors, - // successful expansions - (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { - let new_offset = fake_mapped_token.text_range().start(); - if new_offset > actual_expansion.text_range().end() { - // offset outside of bounds from the original expansion, - // stop here to prevent problems from happening - break 'expansion; - } - original_file = actual_expansion; - speculative_file = fake_expansion; - fake_ident_token = fake_mapped_token; - offset = new_offset; - continue 'expansion; + ) { + // maybe parent items have attributes, so continue walking the ancestors + (None, None) => continue 'ancestors, + // successful expansions + (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { + let new_offset = fake_mapped_token.text_range().start(); + if new_offset > actual_expansion.text_range().end() { + // offset outside of bounds from the original expansion, + // stop here to prevent problems from happening + break 'expansion; } - // exactly one expansion failed, inconsistent state so stop expanding completely - _ => break 'expansion, + original_file = actual_expansion; + speculative_file = fake_expansion; + fake_ident_token = fake_mapped_token; + offset = new_offset; + continue 'expansion; } + // exactly one expansion failed, inconsistent state so stop expanding completely + _ => break 'expansion, } + } - // No attributes have been expanded, so look for macro_call! token trees or derive token trees - let orig_tt = match find_node_at_offset::(&original_file, offset) { - Some(it) => it, - None => break 'expansion, - }; - let spec_tt = match find_node_at_offset::(&speculative_file, offset) { - Some(it) => it, - None => break 'expansion, - }; + // No attributes have been expanded, so look for macro_call! token trees or derive token trees + let orig_tt = match find_node_at_offset::(&original_file, offset) { + Some(it) => it, + None => break 'expansion, + }; + let spec_tt = match find_node_at_offset::(&speculative_file, offset) { + Some(it) => it, + None => break 'expansion, + }; - // Expand pseudo-derive expansion - if let (Some(orig_attr), Some(spec_attr)) = ( - orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()), - spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()), + // Expand pseudo-derive expansion + if let (Some(orig_attr), Some(spec_attr)) = ( + orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()), + spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()), + ) { + if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = ( + sema.expand_derive_as_pseudo_attr_macro(&orig_attr), + sema.speculative_expand_derive_as_pseudo_attr_macro( + &orig_attr, + &spec_attr, + fake_ident_token.clone(), + ), ) { - if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = ( - self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr), - self.sema.speculative_expand_derive_as_pseudo_attr_macro( - &orig_attr, - &spec_attr, - fake_ident_token.clone(), - ), - ) { - derive_ctx = Some(( - actual_expansion, - fake_expansion, - fake_mapped_token.text_range().start(), - orig_attr, - )); - } - // at this point we won't have any more successful expansions, so stop + derive_ctx = Some(( + actual_expansion, + fake_expansion, + fake_mapped_token.text_range().start(), + orig_attr, + )); + } + // at this point we won't have any more successful expansions, so stop + break 'expansion; + } + + // Expand fn-like macro calls + if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( + orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast), + spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast), + ) { + let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text()); + let mac_call_path1 = + macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()); + + // inconsistent state, stop expanding + if mac_call_path0 != mac_call_path1 { break 'expansion; } + let speculative_args = match macro_call_with_fake_ident.token_tree() { + Some(tt) => tt, + None => break 'expansion, + }; - // Expand fn-like macro calls - if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( - orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast), - spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast), + match ( + sema.expand(&actual_macro_call), + sema.speculative_expand( + &actual_macro_call, + &speculative_args, + fake_ident_token.clone(), + ), ) { - let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text()); - let mac_call_path1 = - macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()); - - // inconsistent state, stop expanding - if mac_call_path0 != mac_call_path1 { - break 'expansion; - } - let speculative_args = match macro_call_with_fake_ident.token_tree() { - Some(tt) => tt, - None => break 'expansion, - }; - - match ( - self.sema.expand(&actual_macro_call), - self.sema.speculative_expand( - &actual_macro_call, - &speculative_args, - fake_ident_token.clone(), - ), - ) { - // successful expansions - (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { - let new_offset = fake_mapped_token.text_range().start(); - if new_offset > actual_expansion.text_range().end() { - // offset outside of bounds from the original expansion, - // stop here to prevent problems from happening - break 'expansion; - } - original_file = actual_expansion; - speculative_file = fake_expansion; - fake_ident_token = fake_mapped_token; - offset = new_offset; - continue 'expansion; + // successful expansions + (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { + let new_offset = fake_mapped_token.text_range().start(); + if new_offset > actual_expansion.text_range().end() { + // offset outside of bounds from the original expansion, + // stop here to prevent problems from happening + break 'expansion; } - // at least on expansion failed, we won't have anything to expand from this point - // onwards so break out - _ => break 'expansion, + original_file = actual_expansion; + speculative_file = fake_expansion; + fake_ident_token = fake_mapped_token; + offset = new_offset; + continue 'expansion; } + // at least on expansion failed, we won't have anything to expand from this point + // onwards so break out + _ => break 'expansion, } - - // none of our states have changed so stop the loop - break 'expansion; } - self.analyze(&original_file, speculative_file, offset, derive_ctx) + // none of our states have changed so stop the loop + break 'expansion; } + ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } +} - /// Calculate the expected type and name of the cursor position. - fn expected_type_and_name( - &self, - name_like: &ast::NameLike, - ) -> (Option, Option) { - let mut node = match self.token.parent() { - Some(it) => it, - None => return (None, None), - }; +/// Fill the completion context, this is what does semantic reasoning about the surrounding context +/// of the completion location. +fn analyze( + sema: &Semantics<'_, RootDatabase>, + expansion_result: ExpansionResult, + original_token: &SyntaxToken, + self_token: &SyntaxToken, +) -> Option<(CompletionAnalysis, (Option, Option), QualifierCtx)> { + let _p = profile::span("CompletionContext::analyze"); + let ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } = + expansion_result; + let syntax_element = NodeOrToken::Token(fake_ident_token); + if is_in_token_of_for_loop(syntax_element.clone()) { + // for pat $0 + // there is nothing to complete here except `in` keyword + // don't bother populating the context + // FIXME: the completion calculations should end up good enough + // such that this special case becomes unnecessary + return None; + } - let strip_refs = |mut ty: Type| match name_like { - ast::NameLike::NameRef(n) => { - let p = match n.syntax().parent() { - Some(it) => it, - None => return ty, - }; - let top_syn = match_ast! { - match p { - ast::FieldExpr(e) => e - .syntax() - .ancestors() - .map_while(ast::FieldExpr::cast) - .last() - .map(|it| it.syntax().clone()), - ast::PathSegment(e) => e - .syntax() - .ancestors() - .skip(1) - .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind())) - .find_map(ast::PathExpr::cast) - .map(|it| it.syntax().clone()), - _ => None - } + // Overwrite the path kind for derives + if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx { + if let Some(ast::NameLike::NameRef(name_ref)) = + find_node_at_offset(&file_with_fake_ident, offset) + { + let parent = name_ref.syntax().parent()?; + let (mut nameref_ctx, _) = classify_name_ref(&sema, &original_file, name_ref, parent)?; + if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind { + path_ctx.kind = PathKind::Derive { + existing_derives: sema + .resolve_derive_macro(&origin_attr) + .into_iter() + .flatten() + .flatten() + .collect(), }; - let top_syn = match top_syn { - Some(it) => it, - None => return ty, - }; - for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) { - cov_mark::hit!(expected_type_fn_param_ref); - ty = ty.strip_reference(); - } - ty } - _ => ty, - }; + return Some(( + CompletionAnalysis::NameRef(nameref_ctx), + (None, None), + QualifierCtx::default(), + )); + } + return None; + } - loop { - break match_ast! { - match node { - ast::LetStmt(it) => { - cov_mark::hit!(expected_type_let_with_leading_char); - cov_mark::hit!(expected_type_let_without_leading_char); - let ty = it.pat() - .and_then(|pat| self.sema.type_of_pat(&pat)) - .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it))) - .map(TypeInfo::original); - let name = match it.pat() { - Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name), - Some(_) | None => None, - }; - - (ty, name) - }, - ast::LetExpr(it) => { - cov_mark::hit!(expected_type_if_let_without_leading_char); - let ty = it.pat() - .and_then(|pat| self.sema.type_of_pat(&pat)) - .or_else(|| it.expr().and_then(|it| self.sema.type_of_expr(&it))) - .map(TypeInfo::original); - (ty, None) - }, - ast::ArgList(_) => { - cov_mark::hit!(expected_type_fn_param); - ActiveParameter::at_token( - &self.sema, - self.token.clone(), - ).map(|ap| { - let name = ap.ident().map(NameOrNameRef::Name); - - let ty = strip_refs(ap.ty); - (Some(ty), name) - }) - .unwrap_or((None, None)) - }, - ast::RecordExprFieldList(it) => { - // wouldn't try {} be nice... - (|| { - if self.token.kind() == T![..] - || self.token.prev_token().map(|t| t.kind()) == Some(T![..]) - { - cov_mark::hit!(expected_type_struct_func_update); - let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?; - let ty = self.sema.type_of_expr(&record_expr.into())?; - Some(( - Some(ty.original), - None - )) - } else { - cov_mark::hit!(expected_type_struct_field_without_leading_char); - let expr_field = self.token.prev_sibling_or_token()? - .into_node() - .and_then(ast::RecordExprField::cast)?; - let (_, _, ty) = self.sema.resolve_record_field(&expr_field)?; - Some(( - Some(ty), - expr_field.field_name().map(NameOrNameRef::NameRef), - )) - } - })().unwrap_or((None, None)) - }, - ast::RecordExprField(it) => { - if let Some(expr) = it.expr() { - cov_mark::hit!(expected_type_struct_field_with_leading_char); - ( - self.sema.type_of_expr(&expr).map(TypeInfo::original), - it.field_name().map(NameOrNameRef::NameRef), - ) - } else { - cov_mark::hit!(expected_type_struct_field_followed_by_comma); - let ty = self.sema.resolve_record_field(&it) - .map(|(_, _, ty)| ty); - ( - ty, - it.field_name().map(NameOrNameRef::NameRef), - ) - } - }, - // match foo { $0 } - // match foo { ..., pat => $0 } - ast::MatchExpr(it) => { - let on_arrow = previous_non_trivia_token(self.token.clone()).map_or(false, |it| T![=>] == it.kind()); - - let ty = if on_arrow { - // match foo { ..., pat => $0 } - cov_mark::hit!(expected_type_match_arm_body_without_leading_char); - cov_mark::hit!(expected_type_match_arm_body_with_leading_char); - self.sema.type_of_expr(&it.into()) - } else { - // match foo { $0 } - cov_mark::hit!(expected_type_match_arm_without_leading_char); - it.expr().and_then(|e| self.sema.type_of_expr(&e)) - }.map(TypeInfo::original); - (ty, None) - }, - ast::IfExpr(it) => { - let ty = it.condition() - .and_then(|e| self.sema.type_of_expr(&e)) - .map(TypeInfo::original); - (ty, None) - }, - ast::IdentPat(it) => { - cov_mark::hit!(expected_type_if_let_with_leading_char); - cov_mark::hit!(expected_type_match_arm_with_leading_char); - let ty = self.sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original); - (ty, None) - }, - ast::Fn(it) => { - cov_mark::hit!(expected_type_fn_ret_with_leading_char); - cov_mark::hit!(expected_type_fn_ret_without_leading_char); - let def = self.sema.to_def(&it); - (def.map(|def| def.ret_type(self.db)), None) - }, - ast::ClosureExpr(it) => { - let ty = self.sema.type_of_expr(&it.into()); - ty.and_then(|ty| ty.original.as_callable(self.db)) - .map(|c| (Some(c.return_type()), None)) - .unwrap_or((None, None)) - }, - ast::ParamList(_) => (None, None), - ast::Stmt(_) => (None, None), - ast::Item(_) => (None, None), - _ => { - match node.parent() { - Some(n) => { - node = n; - continue; - }, - None => (None, None), - } - }, + let name_like = match find_node_at_offset(&speculative_file, offset) { + Some(it) => it, + None => { + let analysis = if let Some(original) = ast::String::cast(original_token.clone()) { + CompletionAnalysis::String { + original, + expanded: ast::String::cast(self_token.clone()), + } + } else { + // Fix up trailing whitespace problem + // #[attr(foo = $0 + let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?; + let p = token.parent()?; + if p.kind() == SyntaxKind::TOKEN_TREE + && p.ancestors().any(|it| it.kind() == SyntaxKind::META) + { + let colon_prefix = previous_non_trivia_token(self_token.clone()) + .map_or(false, |it| T![:] == it.kind()); + CompletionAnalysis::UnexpandedAttrTT { + fake_attribute_under_caret: syntax_element + .ancestors() + .find_map(ast::Attr::cast), + colon_prefix, + } + } else { + return None; } }; + return Some((analysis, (None, None), QualifierCtx::default())); } - } - - /// Fill the completion context, this is what does semantic reasoning about the surrounding context - /// of the completion location. - fn analyze( - &mut self, - original_file: &SyntaxNode, - file_with_fake_ident: SyntaxNode, - offset: TextSize, - derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>, - ) -> Option { - let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased()?; - let syntax_element = NodeOrToken::Token(fake_ident_token); - if is_in_token_of_for_loop(syntax_element.clone()) { - // for pat $0 - // there is nothing to complete here except `in` keyword - // don't bother populating the context - // FIXME: the completion calculations should end up good enough - // such that this special case becomes unnecessary - return None; + }; + let expected = expected_type_and_name(sema, &self_token, &name_like); + let mut qual_ctx = QualifierCtx::default(); + let analysis = match name_like { + ast::NameLike::Lifetime(lifetime) => { + CompletionAnalysis::Lifetime(classify_lifetime(sema, &original_file, lifetime)?) + } + ast::NameLike::NameRef(name_ref) => { + let parent = name_ref.syntax().parent()?; + let (nameref_ctx, qualifier_ctx) = + classify_name_ref(sema, &original_file, name_ref, parent.clone())?; + qual_ctx = qualifier_ctx; + CompletionAnalysis::NameRef(nameref_ctx) + } + ast::NameLike::Name(name) => { + let name_ctx = classify_name(sema, &original_file, name)?; + CompletionAnalysis::Name(name_ctx) } + }; + Some((analysis, expected, qual_ctx)) +} - // Overwrite the path kind for derives - if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx { - if let Some(ast::NameLike::NameRef(name_ref)) = - find_node_at_offset(&file_with_fake_ident, offset) - { - let parent = name_ref.syntax().parent()?; - let (mut nameref_ctx, _) = - Self::classify_name_ref(&self.sema, &original_file, name_ref, parent)?; - if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind { - path_ctx.kind = PathKind::Derive { - existing_derives: self - .sema - .resolve_derive_macro(&origin_attr) - .into_iter() - .flatten() - .flatten() - .collect(), - }; +/// Calculate the expected type and name of the cursor position. +fn expected_type_and_name( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, + name_like: &ast::NameLike, +) -> (Option, Option) { + let mut node = match token.parent() { + Some(it) => it, + None => return (None, None), + }; + + let strip_refs = |mut ty: Type| match name_like { + ast::NameLike::NameRef(n) => { + let p = match n.syntax().parent() { + Some(it) => it, + None => return ty, + }; + let top_syn = match_ast! { + match p { + ast::FieldExpr(e) => e + .syntax() + .ancestors() + .map_while(ast::FieldExpr::cast) + .last() + .map(|it| it.syntax().clone()), + ast::PathSegment(e) => e + .syntax() + .ancestors() + .skip(1) + .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind())) + .find_map(ast::PathExpr::cast) + .map(|it| it.syntax().clone()), + _ => None } - return Some(CompletionAnalysis::NameRef(nameref_ctx)); + }; + let top_syn = match top_syn { + Some(it) => it, + None => return ty, + }; + for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) { + cov_mark::hit!(expected_type_fn_param_ref); + ty = ty.strip_reference(); } - return None; + ty } + _ => ty, + }; - let name_like = match find_node_at_offset(&file_with_fake_ident, offset) { - Some(it) => it, - None => { - let analysis = - if let Some(original) = ast::String::cast(self.original_token.clone()) { - CompletionAnalysis::String { - original, - expanded: ast::String::cast(self.token.clone()), - } - } else { - // Fix up trailing whitespace problem - // #[attr(foo = $0 - let token = - syntax::algo::skip_trivia_token(self.token.clone(), Direction::Prev)?; - let p = token.parent()?; - if p.kind() == SyntaxKind::TOKEN_TREE - && p.ancestors().any(|it| it.kind() == SyntaxKind::META) + loop { + break match_ast! { + match node { + ast::LetStmt(it) => { + cov_mark::hit!(expected_type_let_with_leading_char); + cov_mark::hit!(expected_type_let_without_leading_char); + let ty = it.pat() + .and_then(|pat| sema.type_of_pat(&pat)) + .or_else(|| it.initializer().and_then(|it| sema.type_of_expr(&it))) + .map(TypeInfo::original); + let name = match it.pat() { + Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name), + Some(_) | None => None, + }; + + (ty, name) + }, + ast::LetExpr(it) => { + cov_mark::hit!(expected_type_if_let_without_leading_char); + let ty = it.pat() + .and_then(|pat| sema.type_of_pat(&pat)) + .or_else(|| it.expr().and_then(|it| sema.type_of_expr(&it))) + .map(TypeInfo::original); + (ty, None) + }, + ast::ArgList(_) => { + cov_mark::hit!(expected_type_fn_param); + ActiveParameter::at_token( + &sema, + token.clone(), + ).map(|ap| { + let name = ap.ident().map(NameOrNameRef::Name); + + let ty = strip_refs(ap.ty); + (Some(ty), name) + }) + .unwrap_or((None, None)) + }, + ast::RecordExprFieldList(it) => { + // wouldn't try {} be nice... + (|| { + if token.kind() == T![..] + ||token.prev_token().map(|t| t.kind()) == Some(T![..]) { - let colon_prefix = previous_non_trivia_token(self.token.clone()) - .map_or(false, |it| T![:] == it.kind()); - CompletionAnalysis::UnexpandedAttrTT { - fake_attribute_under_caret: syntax_element - .ancestors() - .find_map(ast::Attr::cast), - colon_prefix, - } + cov_mark::hit!(expected_type_struct_func_update); + let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?; + let ty = sema.type_of_expr(&record_expr.into())?; + Some(( + Some(ty.original), + None + )) } else { - return None; + cov_mark::hit!(expected_type_struct_field_without_leading_char); + let expr_field = token.prev_sibling_or_token()? + .into_node() + .and_then(ast::RecordExprField::cast)?; + let (_, _, ty) = sema.resolve_record_field(&expr_field)?; + Some(( + Some(ty), + expr_field.field_name().map(NameOrNameRef::NameRef), + )) } - }; - return Some(analysis); + })().unwrap_or((None, None)) + }, + ast::RecordExprField(it) => { + if let Some(expr) = it.expr() { + cov_mark::hit!(expected_type_struct_field_with_leading_char); + ( + sema.type_of_expr(&expr).map(TypeInfo::original), + it.field_name().map(NameOrNameRef::NameRef), + ) + } else { + cov_mark::hit!(expected_type_struct_field_followed_by_comma); + let ty = sema.resolve_record_field(&it) + .map(|(_, _, ty)| ty); + ( + ty, + it.field_name().map(NameOrNameRef::NameRef), + ) + } + }, + // match foo { $0 } + // match foo { ..., pat => $0 } + ast::MatchExpr(it) => { + let on_arrow = previous_non_trivia_token(token.clone()).map_or(false, |it| T![=>] == it.kind()); + + let ty = if on_arrow { + // match foo { ..., pat => $0 } + cov_mark::hit!(expected_type_match_arm_body_without_leading_char); + cov_mark::hit!(expected_type_match_arm_body_with_leading_char); + sema.type_of_expr(&it.into()) + } else { + // match foo { $0 } + cov_mark::hit!(expected_type_match_arm_without_leading_char); + it.expr().and_then(|e| sema.type_of_expr(&e)) + }.map(TypeInfo::original); + (ty, None) + }, + ast::IfExpr(it) => { + let ty = it.condition() + .and_then(|e| sema.type_of_expr(&e)) + .map(TypeInfo::original); + (ty, None) + }, + ast::IdentPat(it) => { + cov_mark::hit!(expected_type_if_let_with_leading_char); + cov_mark::hit!(expected_type_match_arm_with_leading_char); + let ty = sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original); + (ty, None) + }, + ast::Fn(it) => { + cov_mark::hit!(expected_type_fn_ret_with_leading_char); + cov_mark::hit!(expected_type_fn_ret_without_leading_char); + let def = sema.to_def(&it); + (def.map(|def| def.ret_type(sema.db)), None) + }, + ast::ClosureExpr(it) => { + let ty = sema.type_of_expr(&it.into()); + ty.and_then(|ty| ty.original.as_callable(sema.db)) + .map(|c| (Some(c.return_type()), None)) + .unwrap_or((None, None)) + }, + ast::ParamList(_) => (None, None), + ast::Stmt(_) => (None, None), + ast::Item(_) => (None, None), + _ => { + match node.parent() { + Some(n) => { + node = n; + continue; + }, + None => (None, None), + } + }, } }; - (self.expected_type, self.expected_name) = self.expected_type_and_name(&name_like); - let analysis = match name_like { - ast::NameLike::Lifetime(lifetime) => CompletionAnalysis::Lifetime( - Self::classify_lifetime(&self.sema, original_file, lifetime)?, - ), - ast::NameLike::NameRef(name_ref) => { - let parent = name_ref.syntax().parent()?; - let (nameref_ctx, qualifier_ctx) = - Self::classify_name_ref(&self.sema, &original_file, name_ref, parent.clone())?; + } +} - self.qualifier_ctx = qualifier_ctx; - CompletionAnalysis::NameRef(nameref_ctx) - } - ast::NameLike::Name(name) => { - let name_ctx = Self::classify_name(&self.sema, original_file, name)?; - CompletionAnalysis::Name(name_ctx) - } - }; - Some(analysis) +fn classify_lifetime( + _sema: &Semantics<'_, RootDatabase>, + original_file: &SyntaxNode, + lifetime: ast::Lifetime, +) -> Option { + let parent = lifetime.syntax().parent()?; + if parent.kind() == SyntaxKind::ERROR { + return None; } - fn classify_lifetime( - _sema: &Semantics<'_, RootDatabase>, - original_file: &SyntaxNode, - lifetime: ast::Lifetime, - ) -> Option { - let parent = lifetime.syntax().parent()?; - if parent.kind() == SyntaxKind::ERROR { - return None; + let kind = match_ast! { + match parent { + ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { + is_decl: param.lifetime().as_ref() == Some(&lifetime), + param + }, + ast::BreakExpr(_) => LifetimeKind::LabelRef, + ast::ContinueExpr(_) => LifetimeKind::LabelRef, + ast::Label(_) => LifetimeKind::LabelDef, + _ => LifetimeKind::Lifetime, } + }; + let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start()); - let kind = match_ast! { - match parent { - ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { - is_decl: param.lifetime().as_ref() == Some(&lifetime), - param - }, - ast::BreakExpr(_) => LifetimeKind::LabelRef, - ast::ContinueExpr(_) => LifetimeKind::LabelRef, - ast::Label(_) => LifetimeKind::LabelDef, - _ => LifetimeKind::Lifetime, - } - }; - let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start()); + Some(LifetimeContext { lifetime, kind }) +} - Some(LifetimeContext { lifetime, kind }) - } +fn classify_name( + sema: &Semantics<'_, RootDatabase>, + original_file: &SyntaxNode, + name: ast::Name, +) -> Option { + let parent = name.syntax().parent()?; + let kind = match_ast! { + match parent { + ast::Const(_) => NameKind::Const, + ast::ConstParam(_) => NameKind::ConstParam, + ast::Enum(_) => NameKind::Enum, + ast::Fn(_) => NameKind::Function, + ast::IdentPat(bind_pat) => { + let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into()); + if let Some(record_field) = ast::RecordPatField::for_field_name(&name) { + pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat()); + } - fn classify_name( - sema: &Semantics<'_, RootDatabase>, - original_file: &SyntaxNode, - name: ast::Name, - ) -> Option { - let parent = name.syntax().parent()?; - let kind = match_ast! { - match parent { - ast::Const(_) => NameKind::Const, - ast::ConstParam(_) => NameKind::ConstParam, - ast::Enum(_) => NameKind::Enum, - ast::Fn(_) => NameKind::Function, - ast::IdentPat(bind_pat) => { - let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into()); - if let Some(record_field) = ast::RecordPatField::for_field_name(&name) { - pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat()); - } + NameKind::IdentPat(pat_ctx) + }, + ast::MacroDef(_) => NameKind::MacroDef, + ast::MacroRules(_) => NameKind::MacroRules, + ast::Module(module) => NameKind::Module(module), + ast::RecordField(_) => NameKind::RecordField, + ast::Rename(_) => NameKind::Rename, + ast::SelfParam(_) => NameKind::SelfParam, + ast::Static(_) => NameKind::Static, + ast::Struct(_) => NameKind::Struct, + ast::Trait(_) => NameKind::Trait, + ast::TypeAlias(_) => NameKind::TypeAlias, + ast::TypeParam(_) => NameKind::TypeParam, + ast::Union(_) => NameKind::Union, + ast::Variant(_) => NameKind::Variant, + _ => return None, + } + }; + let name = find_node_at_offset(&original_file, name.syntax().text_range().start()); + Some(NameContext { name, kind }) +} - NameKind::IdentPat(pat_ctx) - }, - ast::MacroDef(_) => NameKind::MacroDef, - ast::MacroRules(_) => NameKind::MacroRules, - ast::Module(module) => NameKind::Module(module), - ast::RecordField(_) => NameKind::RecordField, - ast::Rename(_) => NameKind::Rename, - ast::SelfParam(_) => NameKind::SelfParam, - ast::Static(_) => NameKind::Static, - ast::Struct(_) => NameKind::Struct, - ast::Trait(_) => NameKind::Trait, - ast::TypeAlias(_) => NameKind::TypeAlias, - ast::TypeParam(_) => NameKind::TypeParam, - ast::Union(_) => NameKind::Union, - ast::Variant(_) => NameKind::Variant, - _ => return None, - } - }; - let name = find_node_at_offset(&original_file, name.syntax().text_range().start()); - Some(NameContext { name, kind }) +fn classify_name_ref( + sema: &Semantics<'_, RootDatabase>, + original_file: &SyntaxNode, + name_ref: ast::NameRef, + parent: SyntaxNode, +) -> Option<(NameRefContext, QualifierCtx)> { + let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); + + let make_res = |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default()); + + if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { + let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone()) + .map_or(false, |it| T![.] == it.kind()); + + return find_node_in_file_compensated( + sema, + original_file, + &record_field.parent_record_lit(), + ) + .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix }) + .map(make_res); } - - fn classify_name_ref( - sema: &Semantics<'_, RootDatabase>, - original_file: &SyntaxNode, - name_ref: ast::NameRef, - parent: SyntaxNode, - ) -> Option<(NameRefContext, QualifierCtx)> { - let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - - let make_res = - |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default()); - - if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { - let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone()) - .map_or(false, |it| T![.] == it.kind()); - - return find_node_in_file_compensated( + if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { + let kind = NameRefKind::Pattern(PatternContext { + param_ctx: None, + has_type_ascription: false, + ref_token: None, + mut_token: None, + record_pat: find_node_in_file_compensated( sema, original_file, - &record_field.parent_record_lit(), + &record_field.parent_record_pat(), + ), + ..pattern_context_for( + sema, + original_file, + record_field.parent_record_pat().clone().into(), ) - .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix }) - .map(make_res); + }); + return Some(make_res(kind)); + } + + let segment = match_ast! { + match parent { + ast::PathSegment(segment) => segment, + ast::FieldExpr(field) => { + let receiver = find_opt_node_in_file(original_file, field.expr()); + let receiver_is_ambiguous_float_literal = match &receiver { + Some(ast::Expr::Literal(l)) => matches! { + l.kind(), + ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.')) + }, + _ => false, + }; + let kind = NameRefKind::DotAccess(DotAccess { + receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), + kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, + receiver + }); + return Some(make_res(kind)); + }, + ast::MethodCallExpr(method) => { + let receiver = find_opt_node_in_file(original_file, method.receiver()); + let kind = NameRefKind::DotAccess(DotAccess { + receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), + kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) }, + receiver + }); + return Some(make_res(kind)); + }, + _ => return None, } - if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { - let kind = NameRefKind::Pattern(PatternContext { - param_ctx: None, - has_type_ascription: false, - ref_token: None, - mut_token: None, - record_pat: find_node_in_file_compensated( - sema, - original_file, - &record_field.parent_record_pat(), - ), - ..pattern_context_for( - sema, - original_file, - record_field.parent_record_pat().clone().into(), - ) - }); - return Some(make_res(kind)); + }; + + let path = segment.parent_path(); + let original_path = find_node_in_file_compensated(sema, original_file, &path); + + let mut path_ctx = PathCompletionCtx { + has_call_parens: false, + has_macro_bang: false, + qualified: Qualified::No, + parent: None, + path: path.clone(), + original_path, + kind: PathKind::Item { kind: ItemListKind::SourceFile }, + has_type_args: false, + use_tree_parent: false, + }; + + let is_in_block = |it: &SyntaxNode| { + it.parent() + .map(|node| { + ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind()) + }) + .unwrap_or(false) + }; + let func_update_record = |syn: &SyntaxNode| { + if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) { + find_node_in_file_compensated(sema, original_file, &record_expr) + } else { + None + } + }; + let after_if_expr = |node: SyntaxNode| { + let prev_expr = (|| { + let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; + ast::ExprStmt::cast(prev_sibling)?.expr() + })(); + matches!(prev_expr, Some(ast::Expr::IfExpr(_))) + }; + + // We do not want to generate path completions when we are sandwiched between an item decl signature and its body. + // ex. trait Foo $0 {} + // in these cases parser recovery usually kicks in for our inserted identifier, causing it + // to either be parsed as an ExprStmt or a MacroCall, depending on whether it is in a block + // expression or an item list. + // The following code checks if the body is missing, if it is we either cut off the body + // from the item or it was missing in the first place + let inbetween_body_and_decl_check = |node: SyntaxNode| { + if let Some(NodeOrToken::Node(n)) = + syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev) + { + if let Some(item) = ast::Item::cast(n) { + let is_inbetween = match &item { + ast::Item::Const(it) => it.body().is_none(), + ast::Item::Enum(it) => it.variant_list().is_none(), + ast::Item::ExternBlock(it) => it.extern_item_list().is_none(), + ast::Item::Fn(it) => it.body().is_none(), + ast::Item::Impl(it) => it.assoc_item_list().is_none(), + ast::Item::Module(it) => it.item_list().is_none(), + ast::Item::Static(it) => it.body().is_none(), + ast::Item::Struct(it) => it.field_list().is_none(), + ast::Item::Trait(it) => it.assoc_item_list().is_none(), + ast::Item::TypeAlias(it) => it.ty().is_none(), + ast::Item::Union(it) => it.record_field_list().is_none(), + _ => false, + }; + if is_inbetween { + return Some(item); + } + } } + None + }; - let segment = match_ast! { + let type_location = |node: &SyntaxNode| { + let parent = node.parent()?; + let res = match_ast! { match parent { - ast::PathSegment(segment) => segment, - ast::FieldExpr(field) => { - let receiver = find_opt_node_in_file(original_file, field.expr()); - let receiver_is_ambiguous_float_literal = match &receiver { - Some(ast::Expr::Literal(l)) => matches! { - l.kind(), - ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.')) - }, - _ => false, + ast::Const(it) => { + let name = find_opt_node_in_file(original_file, it.name())?; + let original = ast::Const::cast(name.syntax().parent()?)?; + TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) + }, + ast::RetType(it) => { + if it.thin_arrow_token().is_none() { + return None; + } + let parent = match ast::Fn::cast(parent.parent()?) { + Some(x) => x.param_list(), + None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(), }; - let kind = NameRefKind::DotAccess(DotAccess { - receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), - kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, - receiver - }); - return Some(make_res(kind)); + + let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?; + TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! { + match parent { + ast::ClosureExpr(it) => { + it.body() + }, + ast::Fn(it) => { + it.body().map(ast::Expr::BlockExpr) + }, + _ => return None, + } + })) }, - ast::MethodCallExpr(method) => { - let receiver = find_opt_node_in_file(original_file, method.receiver()); - let kind = NameRefKind::DotAccess(DotAccess { - receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), - kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) }, - receiver - }); - return Some(make_res(kind)); + ast::Param(it) => { + if it.colon_token().is_none() { + return None; + } + TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat()))) }, + ast::LetStmt(it) => { + if it.colon_token().is_none() { + return None; + } + TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat()))) + }, + ast::Impl(it) => { + match it.trait_() { + Some(t) if t.syntax() == node => TypeLocation::ImplTrait, + _ => match it.self_ty() { + Some(t) if t.syntax() == node => TypeLocation::ImplTarget, + _ => return None, + }, + } + }, + ast::TypeBound(_) => TypeLocation::TypeBound, + // is this case needed? + ast::TypeBoundList(_) => TypeLocation::TypeBound, + ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))), + // is this case needed? + ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))), + ast::TupleField(_) => TypeLocation::TupleField, _ => return None, } }; + Some(res) + }; - let path = segment.parent_path(); - let original_path = find_node_in_file_compensated(sema, original_file, &path); - - let mut path_ctx = PathCompletionCtx { - has_call_parens: false, - has_macro_bang: false, - qualified: Qualified::No, - parent: None, - path: path.clone(), - original_path, - kind: PathKind::Item { kind: ItemListKind::SourceFile }, - has_type_args: false, - use_tree_parent: false, - }; - - let is_in_block = |it: &SyntaxNode| { - it.parent() - .map(|node| { - ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind()) - }) - .unwrap_or(false) - }; - let func_update_record = |syn: &SyntaxNode| { - if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) { - find_node_in_file_compensated(sema, original_file, &record_expr) + let is_in_condition = |it: &ast::Expr| { + (|| { + let parent = it.syntax().parent()?; + if let Some(expr) = ast::WhileExpr::cast(parent.clone()) { + Some(expr.condition()? == *it) + } else if let Some(expr) = ast::IfExpr::cast(parent) { + Some(expr.condition()? == *it) } else { None } - }; - let after_if_expr = |node: SyntaxNode| { - let prev_expr = (|| { - let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; - ast::ExprStmt::cast(prev_sibling)?.expr() - })(); - matches!(prev_expr, Some(ast::Expr::IfExpr(_))) - }; + })() + .unwrap_or(false) + }; - // We do not want to generate path completions when we are sandwiched between an item decl signature and its body. - // ex. trait Foo $0 {} - // in these cases parser recovery usually kicks in for our inserted identifier, causing it - // to either be parsed as an ExprStmt or a MacroCall, depending on whether it is in a block - // expression or an item list. - // The following code checks if the body is missing, if it is we either cut off the body - // from the item or it was missing in the first place - let inbetween_body_and_decl_check = |node: SyntaxNode| { - if let Some(NodeOrToken::Node(n)) = - syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev) - { - if let Some(item) = ast::Item::cast(n) { - let is_inbetween = match &item { - ast::Item::Const(it) => it.body().is_none(), - ast::Item::Enum(it) => it.variant_list().is_none(), - ast::Item::ExternBlock(it) => it.extern_item_list().is_none(), - ast::Item::Fn(it) => it.body().is_none(), - ast::Item::Impl(it) => it.assoc_item_list().is_none(), - ast::Item::Module(it) => it.item_list().is_none(), - ast::Item::Static(it) => it.body().is_none(), - ast::Item::Struct(it) => it.field_list().is_none(), - ast::Item::Trait(it) => it.assoc_item_list().is_none(), - ast::Item::TypeAlias(it) => it.ty().is_none(), - ast::Item::Union(it) => it.record_field_list().is_none(), - _ => false, - }; - if is_inbetween { - return Some(item); + let make_path_kind_expr = |expr: ast::Expr| { + let it = expr.syntax(); + let in_block_expr = is_in_block(it); + let in_loop_body = is_in_loop_body(it); + let after_if_expr = after_if_expr(it.clone()); + let ref_expr_parent = + path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast); + let (innermost_ret_ty, self_param) = { + let find_ret_ty = |it: SyntaxNode| { + if let Some(item) = ast::Item::cast(it.clone()) { + match item { + ast::Item::Fn(f) => Some(sema.to_def(&f).map(|it| it.ret_type(sema.db))), + ast::Item::MacroCall(_) => None, + _ => Some(None), } - } - } - None - }; - - let type_location = |node: &SyntaxNode| { - let parent = node.parent()?; - let res = match_ast! { - match parent { - ast::Const(it) => { - let name = find_opt_node_in_file(original_file, it.name())?; - let original = ast::Const::cast(name.syntax().parent()?)?; - TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) - }, - ast::RetType(it) => { - if it.thin_arrow_token().is_none() { - return None; - } - let parent = match ast::Fn::cast(parent.parent()?) { - Some(x) => x.param_list(), - None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(), - }; - - let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?; - TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! { - match parent { - ast::ClosureExpr(it) => { - it.body() - }, - ast::Fn(it) => { - it.body().map(ast::Expr::BlockExpr) - }, - _ => return None, - } - })) - }, - ast::Param(it) => { - if it.colon_token().is_none() { - return None; - } - TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat()))) - }, - ast::LetStmt(it) => { - if it.colon_token().is_none() { - return None; - } - TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat()))) - }, - ast::Impl(it) => { - match it.trait_() { - Some(t) if t.syntax() == node => TypeLocation::ImplTrait, - _ => match it.self_ty() { - Some(t) if t.syntax() == node => TypeLocation::ImplTarget, - _ => return None, - }, - } - }, - ast::TypeBound(_) => TypeLocation::TypeBound, - // is this case needed? - ast::TypeBoundList(_) => TypeLocation::TypeBound, - ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))), - // is this case needed? - ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))), - ast::TupleField(_) => TypeLocation::TupleField, - _ => return None, - } - }; - Some(res) - }; - - let is_in_condition = |it: &ast::Expr| { - (|| { - let parent = it.syntax().parent()?; - if let Some(expr) = ast::WhileExpr::cast(parent.clone()) { - Some(expr.condition()? == *it) - } else if let Some(expr) = ast::IfExpr::cast(parent) { - Some(expr.condition()? == *it) } else { - None - } - })() - .unwrap_or(false) - }; - - let make_path_kind_expr = |expr: ast::Expr| { - let it = expr.syntax(); - let in_block_expr = is_in_block(it); - let in_loop_body = is_in_loop_body(it); - let after_if_expr = after_if_expr(it.clone()); - let ref_expr_parent = - path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast); - let (innermost_ret_ty, self_param) = { - let find_ret_ty = |it: SyntaxNode| { - if let Some(item) = ast::Item::cast(it.clone()) { - match item { - ast::Item::Fn(f) => { - Some(sema.to_def(&f).map(|it| it.ret_type(sema.db))) - } - ast::Item::MacroCall(_) => None, - _ => Some(None), - } - } else { - let expr = ast::Expr::cast(it)?; - let callable = match expr { - // FIXME - // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b), - ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr), - _ => return None, - }; - Some( - callable - .and_then(|c| c.adjusted().as_callable(sema.db)) - .map(|it| it.return_type()), - ) - } - }; - let find_fn_self_param = |it| match it { - ast::Item::Fn(fn_) => { - Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db))) - } - ast::Item::MacroCall(_) => None, - _ => Some(None), - }; - - match find_node_in_file_compensated(sema, original_file, &expr) { - Some(it) => { - let innermost_ret_ty = sema - .ancestors_with_macros(it.syntax().clone()) - .find_map(find_ret_ty) - .flatten(); - - let self_param = sema - .ancestors_with_macros(it.syntax().clone()) - .filter_map(ast::Item::cast) - .find_map(find_fn_self_param) - .flatten(); - (innermost_ret_ty, self_param) - } - None => (None, None), + let expr = ast::Expr::cast(it)?; + let callable = match expr { + // FIXME + // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b), + ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr), + _ => return None, + }; + Some( + callable + .and_then(|c| c.adjusted().as_callable(sema.db)) + .map(|it| it.return_type()), + ) } }; - let is_func_update = func_update_record(it); - let in_condition = is_in_condition(&expr); - let incomplete_let = it - .parent() - .and_then(ast::LetStmt::cast) - .map_or(false, |it| it.semicolon_token().is_none()); - let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax()); - - let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) { - Some(arm) => arm - .fat_arrow_token() - .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()), - None => false, + let find_fn_self_param = |it| match it { + ast::Item::Fn(fn_) => Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db))), + ast::Item::MacroCall(_) => None, + _ => Some(None), }; - PathKind::Expr { - expr_ctx: ExprCtx { - in_block_expr, - in_loop_body, - after_if_expr, - in_condition, - ref_expr_parent, - is_func_update, - innermost_ret_ty, - self_param, - incomplete_let, - impl_, - in_match_guard, - }, + match find_node_in_file_compensated(sema, original_file, &expr) { + Some(it) => { + let innermost_ret_ty = sema + .ancestors_with_macros(it.syntax().clone()) + .find_map(find_ret_ty) + .flatten(); + + let self_param = sema + .ancestors_with_macros(it.syntax().clone()) + .filter_map(ast::Item::cast) + .find_map(find_fn_self_param) + .flatten(); + (innermost_ret_ty, self_param) + } + None => (None, None), } }; - let make_path_kind_type = |ty: ast::Type| { - let location = type_location(ty.syntax()); - PathKind::Type { location: location.unwrap_or(TypeLocation::Other) } + let is_func_update = func_update_record(it); + let in_condition = is_in_condition(&expr); + let incomplete_let = it + .parent() + .and_then(ast::LetStmt::cast) + .map_or(false, |it| it.semicolon_token().is_none()); + let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax()); + + let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) { + Some(arm) => arm + .fat_arrow_token() + .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()), + None => false, }; - let mut kind_macro_call = |it: ast::MacroCall| { - path_ctx.has_macro_bang = it.excl_token().is_some(); - let parent = it.syntax().parent()?; - // Any path in an item list will be treated as a macro call by the parser - let kind = match_ast! { - match parent { - ast::MacroExpr(expr) => make_path_kind_expr(expr.into()), - ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}, - ast::MacroType(ty) => make_path_kind_type(ty.into()), - ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module }, - ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() { - Some(it) => match_ast! { - match it { - ast::Trait(_) => ItemListKind::Trait, - ast::Impl(it) => if it.trait_().is_some() { - ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it)) - } else { - ItemListKind::Impl - }, - _ => return None - } - }, - None => return None, - } }, - ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock }, - ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile }, - _ => return None, - } - }; - Some(kind) - }; - let make_path_kind_attr = |meta: ast::Meta| { - let attr = meta.parent_attr()?; - let kind = attr.kind(); - let attached = attr.syntax().parent()?; - let is_trailing_outer_attr = kind != AttrKind::Inner - && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next) - .is_none(); - let annotated_item_kind = - if is_trailing_outer_attr { None } else { Some(attached.kind()) }; - Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } }) - }; + PathKind::Expr { + expr_ctx: ExprCtx { + in_block_expr, + in_loop_body, + after_if_expr, + in_condition, + ref_expr_parent, + is_func_update, + innermost_ret_ty, + self_param, + incomplete_let, + impl_, + in_match_guard, + }, + } + }; + let make_path_kind_type = |ty: ast::Type| { + let location = type_location(ty.syntax()); + PathKind::Type { location: location.unwrap_or(TypeLocation::Other) } + }; - // Infer the path kind - let parent = path.syntax().parent()?; + let mut kind_macro_call = |it: ast::MacroCall| { + path_ctx.has_macro_bang = it.excl_token().is_some(); + let parent = it.syntax().parent()?; + // Any path in an item list will be treated as a macro call by the parser let kind = match_ast! { match parent { - ast::PathType(it) => make_path_kind_type(it.into()), - ast::PathExpr(it) => { - if let Some(p) = it.syntax().parent() { - if ast::ExprStmt::can_cast(p.kind()) { - if let Some(kind) = inbetween_body_and_decl_check(p) { - return Some(make_res(NameRefKind::Keyword(kind))); - } - } - } - - path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); - - make_path_kind_expr(it.into()) - }, - ast::TupleStructPat(it) => { - path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } - }, - ast::RecordPat(it) => { - path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } - }, - ast::PathPat(it) => { - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} - }, - 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))); - } - - kind_macro_call(it)? - }, - ast::Meta(meta) => make_path_kind_attr(meta)?, - ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() }, - ast::UseTree(_) => PathKind::Use, - // completing inside a qualifier - ast::Path(parent) => { - path_ctx.parent = Some(parent.clone()); - let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?; - match_ast! { - match parent { - ast::PathType(it) => make_path_kind_type(it.into()), - ast::PathExpr(it) => { - path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); - - make_path_kind_expr(it.into()) - }, - ast::TupleStructPat(it) => { - path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } - }, - ast::RecordPat(it) => { - path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } - }, - ast::PathPat(it) => { - PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} - }, - ast::MacroCall(it) => { - kind_macro_call(it)? + ast::MacroExpr(expr) => make_path_kind_expr(expr.into()), + ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}, + ast::MacroType(ty) => make_path_kind_type(ty.into()), + ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module }, + ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() { + Some(it) => match_ast! { + match it { + ast::Trait(_) => ItemListKind::Trait, + ast::Impl(it) => if it.trait_().is_some() { + ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it)) + } else { + ItemListKind::Impl }, - ast::Meta(meta) => make_path_kind_attr(meta)?, - ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() }, - ast::UseTree(_) => PathKind::Use, - ast::RecordExpr(it) => make_path_kind_expr(it.into()), - _ => return None, + _ => return None } - } - }, - ast::RecordExpr(it) => make_path_kind_expr(it.into()), + }, + None => return None, + } }, + ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock }, + ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile }, _ => return None, } }; + Some(kind) + }; + let make_path_kind_attr = |meta: ast::Meta| { + let attr = meta.parent_attr()?; + let kind = attr.kind(); + let attached = attr.syntax().parent()?; + let is_trailing_outer_attr = kind != AttrKind::Inner + && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none(); + let annotated_item_kind = if is_trailing_outer_attr { None } else { Some(attached.kind()) }; + Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } }) + }; - path_ctx.kind = kind; - path_ctx.has_type_args = segment.generic_arg_list().is_some(); + // Infer the path kind + let parent = path.syntax().parent()?; + let kind = match_ast! { + match parent { + ast::PathType(it) => make_path_kind_type(it.into()), + ast::PathExpr(it) => { + if let Some(p) = it.syntax().parent() { + if ast::ExprStmt::can_cast(p.kind()) { + if let Some(kind) = inbetween_body_and_decl_check(p) { + return Some(make_res(NameRefKind::Keyword(kind))); + } + } + } - // calculate the qualifier context - if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) { - path_ctx.use_tree_parent = use_tree_parent; - if !use_tree_parent && segment.coloncolon_token().is_some() { - path_ctx.qualified = Qualified::Absolute; - } else { - let qualifier = qualifier - .segment() - .and_then(|it| find_node_in_file(original_file, &it)) - .map(|it| it.parent_path()); - if let Some(qualifier) = qualifier { - let type_anchor = match qualifier.segment().and_then(|it| it.kind()) { - Some(ast::PathSegmentKind::Type { - type_ref: Some(type_ref), - trait_ref, - }) if qualifier.qualifier().is_none() => Some((type_ref, trait_ref)), - _ => None, - }; + path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); + + make_path_kind_expr(it.into()) + }, + ast::TupleStructPat(it) => { + path_ctx.has_call_parens = true; + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } + }, + ast::RecordPat(it) => { + path_ctx.has_call_parens = true; + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } + }, + ast::PathPat(it) => { + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} + }, + 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))); + } - path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor { - let ty = match ty { - ast::Type::InferType(_) => None, - ty => sema.resolve_type(&ty), - }; - let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?)); - Qualified::TypeAnchor { ty, trait_ } - } else { - let res = sema.resolve_path(&qualifier); - - // For understanding how and why super_chain_len is calculated the way it - // is check the documentation at it's definition - let mut segment_count = 0; - let super_count = - iter::successors(Some(qualifier.clone()), |p| p.qualifier()) - .take_while(|p| { - p.segment() - .and_then(|s| { - segment_count += 1; - s.super_token() - }) - .is_some() - }) - .count(); + kind_macro_call(it)? + }, + ast::Meta(meta) => make_path_kind_attr(meta)?, + ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() }, + ast::UseTree(_) => PathKind::Use, + // completing inside a qualifier + ast::Path(parent) => { + path_ctx.parent = Some(parent.clone()); + let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?; + match_ast! { + match parent { + ast::PathType(it) => make_path_kind_type(it.into()), + ast::PathExpr(it) => { + path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); + + make_path_kind_expr(it.into()) + }, + ast::TupleStructPat(it) => { + path_ctx.has_call_parens = true; + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } + }, + ast::RecordPat(it) => { + path_ctx.has_call_parens = true; + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) } + }, + ast::PathPat(it) => { + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} + }, + ast::MacroCall(it) => { + kind_macro_call(it)? + }, + ast::Meta(meta) => make_path_kind_attr(meta)?, + ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() }, + ast::UseTree(_) => PathKind::Use, + ast::RecordExpr(it) => make_path_kind_expr(it.into()), + _ => return None, + } + } + }, + ast::RecordExpr(it) => make_path_kind_expr(it.into()), + _ => return None, + } + }; - let super_chain_len = - if segment_count > super_count { None } else { Some(super_count) }; + path_ctx.kind = kind; + path_ctx.has_type_args = segment.generic_arg_list().is_some(); - Qualified::With { path: qualifier, resolution: res, super_chain_len } + // calculate the qualifier context + if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) { + path_ctx.use_tree_parent = use_tree_parent; + if !use_tree_parent && segment.coloncolon_token().is_some() { + path_ctx.qualified = Qualified::Absolute; + } else { + let qualifier = qualifier + .segment() + .and_then(|it| find_node_in_file(original_file, &it)) + .map(|it| it.parent_path()); + if let Some(qualifier) = qualifier { + let type_anchor = match qualifier.segment().and_then(|it| it.kind()) { + Some(ast::PathSegmentKind::Type { type_ref: Some(type_ref), trait_ref }) + if qualifier.qualifier().is_none() => + { + Some((type_ref, trait_ref)) } + _ => None, }; - } - } else if let Some(segment) = path.segment() { - if segment.coloncolon_token().is_some() { - path_ctx.qualified = Qualified::Absolute; - } - } - let mut qualifier_ctx = QualifierCtx::default(); - if path_ctx.is_trivial_path() { - // fetch the full expression that may have qualifiers attached to it - let top_node = match path_ctx.kind { - PathKind::Expr { expr_ctx: ExprCtx { in_block_expr: true, .. } } => { - parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| { - let parent = p.parent()?; - if ast::StmtList::can_cast(parent.kind()) { - Some(p) - } else if ast::ExprStmt::can_cast(parent.kind()) { - Some(parent) - } else { - None - } - }) - } - PathKind::Item { .. } => { - parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind())) + path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor { + let ty = match ty { + ast::Type::InferType(_) => None, + ty => sema.resolve_type(&ty), + }; + let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?)); + Qualified::TypeAnchor { ty, trait_ } + } else { + let res = sema.resolve_path(&qualifier); + + // For understanding how and why super_chain_len is calculated the way it + // is check the documentation at it's definition + let mut segment_count = 0; + let super_count = iter::successors(Some(qualifier.clone()), |p| p.qualifier()) + .take_while(|p| { + p.segment() + .and_then(|s| { + segment_count += 1; + s.super_token() + }) + .is_some() + }) + .count(); + + let super_chain_len = + if segment_count > super_count { None } else { Some(super_count) }; + + Qualified::With { path: qualifier, resolution: res, super_chain_len } } - _ => None, }; - if let Some(top) = top_node { - if let Some(NodeOrToken::Node(error_node)) = - syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev) - { - if error_node.kind() == SyntaxKind::ERROR { - qualifier_ctx.unsafe_tok = error_node - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|it| it.kind() == T![unsafe]); - qualifier_ctx.vis_node = - error_node.children().find_map(ast::Visibility::cast); + } + } else if let Some(segment) = path.segment() { + if segment.coloncolon_token().is_some() { + path_ctx.qualified = Qualified::Absolute; + } + } + + let mut qualifier_ctx = QualifierCtx::default(); + if path_ctx.is_trivial_path() { + // fetch the full expression that may have qualifiers attached to it + let top_node = match path_ctx.kind { + PathKind::Expr { expr_ctx: ExprCtx { in_block_expr: true, .. } } => { + parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| { + let parent = p.parent()?; + if ast::StmtList::can_cast(parent.kind()) { + Some(p) + } else if ast::ExprStmt::can_cast(parent.kind()) { + Some(parent) + } else { + None } + }) + } + PathKind::Item { .. } => { + parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind())) + } + _ => None, + }; + if let Some(top) = top_node { + if let Some(NodeOrToken::Node(error_node)) = + syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev) + { + if error_node.kind() == SyntaxKind::ERROR { + qualifier_ctx.unsafe_tok = error_node + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|it| it.kind() == T![unsafe]); + qualifier_ctx.vis_node = error_node.children().find_map(ast::Visibility::cast); } + } - if let PathKind::Item { .. } = path_ctx.kind { - if qualifier_ctx.none() { - if let Some(t) = top.first_token() { - if let Some(prev) = t - .prev_token() - .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev)) - { - if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) { - // This was inferred to be an item position path, but it seems - // to be part of some other broken node which leaked into an item - // list - return None; - } + if let PathKind::Item { .. } = path_ctx.kind { + if qualifier_ctx.none() { + if let Some(t) = top.first_token() { + if let Some(prev) = t + .prev_token() + .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev)) + { + if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) { + // This was inferred to be an item position path, but it seems + // to be part of some other broken node which leaked into an item + // list + return None; } } } } } } - Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx)) } + Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx)) } fn pattern_context_for(