mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-11-03 13:13:18 +00:00 
			
		
		
		
	Auto merge of #12025 - Veykril:completion-ctx, r=Veykril
minor: Document completion context some more
This commit is contained in:
		
						commit
						838cc9d3cc
					
				@ -148,7 +148,11 @@ pub(crate) struct CompletionContext<'a> {
 | 
				
			|||||||
    pub(super) krate: hir::Crate,
 | 
					    pub(super) krate: hir::Crate,
 | 
				
			||||||
    /// The module of the `scope`.
 | 
					    /// The module of the `scope`.
 | 
				
			||||||
    pub(super) module: hir::Module,
 | 
					    pub(super) module: hir::Module,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The expected name of what we are completing.
 | 
				
			||||||
 | 
					    /// This is usually the parameter name of the function argument we are completing.
 | 
				
			||||||
    pub(super) expected_name: Option<NameOrNameRef>,
 | 
					    pub(super) expected_name: Option<NameOrNameRef>,
 | 
				
			||||||
 | 
					    /// The expected type of what we are completing.
 | 
				
			||||||
    pub(super) expected_type: Option<Type>,
 | 
					    pub(super) expected_type: Option<Type>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// The parent function of the cursor position if it exists.
 | 
					    /// The parent function of the cursor position if it exists.
 | 
				
			||||||
@ -157,6 +161,7 @@ pub(crate) struct CompletionContext<'a> {
 | 
				
			|||||||
    pub(super) impl_def: Option<ast::Impl>,
 | 
					    pub(super) impl_def: Option<ast::Impl>,
 | 
				
			||||||
    /// The NameLike under the cursor in the original file if it exists.
 | 
					    /// The NameLike under the cursor in the original file if it exists.
 | 
				
			||||||
    pub(super) name_syntax: Option<ast::NameLike>,
 | 
					    pub(super) name_syntax: Option<ast::NameLike>,
 | 
				
			||||||
 | 
					    /// Are we completing inside a let statement with a missing semicolon?
 | 
				
			||||||
    pub(super) incomplete_let: bool,
 | 
					    pub(super) incomplete_let: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(super) completion_location: Option<ImmediateLocation>,
 | 
					    pub(super) completion_location: Option<ImmediateLocation>,
 | 
				
			||||||
@ -424,6 +429,7 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
        let scope = sema.scope_at_offset(&token.parent()?, offset)?;
 | 
					        let scope = sema.scope_at_offset(&token.parent()?, offset)?;
 | 
				
			||||||
        let krate = scope.krate();
 | 
					        let krate = scope.krate();
 | 
				
			||||||
        let module = scope.module();
 | 
					        let module = scope.module();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut locals = FxHashMap::default();
 | 
					        let mut locals = FxHashMap::default();
 | 
				
			||||||
        scope.process_all_names(&mut |name, scope| {
 | 
					        scope.process_all_names(&mut |name, scope| {
 | 
				
			||||||
            if let ScopeDef::Local(local) = scope {
 | 
					            if let ScopeDef::Local(local) = scope {
 | 
				
			||||||
@ -467,8 +473,9 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
        Some(ctx)
 | 
					        Some(ctx)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Do the attribute expansion at the current cursor position for both original file and fake file
 | 
					    /// Expand attributes and macro calls at the current cursor position for both the original file
 | 
				
			||||||
    /// as long as possible. As soon as one of the two expansions fail we stop to stay in sync.
 | 
					    /// 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_and_fill(
 | 
					    fn expand_and_fill(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
        mut original_file: SyntaxNode,
 | 
					        mut original_file: SyntaxNode,
 | 
				
			||||||
@ -489,7 +496,9 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
                ),
 | 
					                ),
 | 
				
			||||||
                |(a, b)| parent_item(a).zip(parent_item(b)),
 | 
					                |(a, b)| parent_item(a).zip(parent_item(b)),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            for (actual_item, item_with_fake_ident) in ancestor_items {
 | 
					
 | 
				
			||||||
 | 
					            // 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 (
 | 
					                match (
 | 
				
			||||||
                    self.sema.expand_attr_macro(&actual_item),
 | 
					                    self.sema.expand_attr_macro(&actual_item),
 | 
				
			||||||
                    self.sema.speculative_expand_attr_macro(
 | 
					                    self.sema.speculative_expand_attr_macro(
 | 
				
			||||||
@ -498,12 +507,14 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
                        fake_ident_token.clone(),
 | 
					                        fake_ident_token.clone(),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    // maybe parent items have attributes
 | 
					                    // maybe parent items have attributes, so continue walking the ancestors
 | 
				
			||||||
                    (None, None) => (),
 | 
					                    (None, None) => continue 'ancestors,
 | 
				
			||||||
                    // successful expansions
 | 
					                    // successful expansions
 | 
				
			||||||
                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
 | 
					                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
 | 
				
			||||||
                        let new_offset = fake_mapped_token.text_range().start();
 | 
					                        let new_offset = fake_mapped_token.text_range().start();
 | 
				
			||||||
                        if new_offset > actual_expansion.text_range().end() {
 | 
					                        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;
 | 
					                            break 'expansion;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        original_file = actual_expansion;
 | 
					                        original_file = actual_expansion;
 | 
				
			||||||
@ -516,13 +527,15 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
                    _ => break 'expansion,
 | 
					                    _ => 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::<ast::TokenTree>(&original_file, offset) {
 | 
					            let orig_tt = match find_node_at_offset::<ast::TokenTree>(&original_file, offset) {
 | 
				
			||||||
                Some(it) => it,
 | 
					                Some(it) => it,
 | 
				
			||||||
                None => break,
 | 
					                None => break 'expansion,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
 | 
					            let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
 | 
				
			||||||
                Some(it) => it,
 | 
					                Some(it) => it,
 | 
				
			||||||
                None => break,
 | 
					                None => break 'expansion,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Expand pseudo-derive expansion
 | 
					            // Expand pseudo-derive expansion
 | 
				
			||||||
@ -530,7 +543,7 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
                orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_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()),
 | 
					                spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                match (
 | 
					                if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
 | 
				
			||||||
                    self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
 | 
					                    self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
 | 
				
			||||||
                    self.sema.speculative_expand_derive_as_pseudo_attr_macro(
 | 
					                    self.sema.speculative_expand_derive_as_pseudo_attr_macro(
 | 
				
			||||||
                        &orig_attr,
 | 
					                        &orig_attr,
 | 
				
			||||||
@ -538,19 +551,16 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
                        fake_ident_token.clone(),
 | 
					                        fake_ident_token.clone(),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    // Clearly not a derive macro
 | 
					                    derive_ctx = Some((
 | 
				
			||||||
                    (None, None) => (),
 | 
					                        actual_expansion,
 | 
				
			||||||
                    // successful expansions
 | 
					                        fake_expansion,
 | 
				
			||||||
                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
 | 
					                        fake_mapped_token.text_range().start(),
 | 
				
			||||||
                        let new_offset = fake_mapped_token.text_range().start();
 | 
					                        orig_attr,
 | 
				
			||||||
                        derive_ctx =
 | 
					                    ));
 | 
				
			||||||
                            Some((actual_expansion, fake_expansion, new_offset, orig_attr));
 | 
					                }
 | 
				
			||||||
 | 
					                // at this point we won't have any more successful expansions, so stop
 | 
				
			||||||
                break 'expansion;
 | 
					                break 'expansion;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                    // exactly one expansion failed, inconsistent state so stop expanding completely
 | 
					 | 
				
			||||||
                    _ => break 'expansion,
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Expand fn-like macro calls
 | 
					            // Expand fn-like macro calls
 | 
				
			||||||
            if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
 | 
					            if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
 | 
				
			||||||
@ -560,12 +570,14 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
                let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
 | 
					                let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
 | 
				
			||||||
                let mac_call_path1 =
 | 
					                let mac_call_path1 =
 | 
				
			||||||
                    macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
 | 
					                    macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // inconsistent state, stop expanding
 | 
				
			||||||
                if mac_call_path0 != mac_call_path1 {
 | 
					                if mac_call_path0 != mac_call_path1 {
 | 
				
			||||||
                    break;
 | 
					                    break 'expansion;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                let speculative_args = match macro_call_with_fake_ident.token_tree() {
 | 
					                let speculative_args = match macro_call_with_fake_ident.token_tree() {
 | 
				
			||||||
                    Some(tt) => tt,
 | 
					                    Some(tt) => tt,
 | 
				
			||||||
                    None => break,
 | 
					                    None => break 'expansion,
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match (
 | 
					                match (
 | 
				
			||||||
@ -580,24 +592,30 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
 | 
					                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
 | 
				
			||||||
                        let new_offset = fake_mapped_token.text_range().start();
 | 
					                        let new_offset = fake_mapped_token.text_range().start();
 | 
				
			||||||
                        if new_offset > actual_expansion.text_range().end() {
 | 
					                        if new_offset > actual_expansion.text_range().end() {
 | 
				
			||||||
                            break;
 | 
					                            // offset outside of bounds from the original expansion,
 | 
				
			||||||
 | 
					                            // stop here to prevent problems from happening
 | 
				
			||||||
 | 
					                            break 'expansion;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        original_file = actual_expansion;
 | 
					                        original_file = actual_expansion;
 | 
				
			||||||
                        speculative_file = fake_expansion;
 | 
					                        speculative_file = fake_expansion;
 | 
				
			||||||
                        fake_ident_token = fake_mapped_token;
 | 
					                        fake_ident_token = fake_mapped_token;
 | 
				
			||||||
                        offset = new_offset;
 | 
					                        offset = new_offset;
 | 
				
			||||||
                        continue;
 | 
					                        continue 'expansion;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    _ => break,
 | 
					                    // at least on expansion failed, we won't have anything to expand from this point
 | 
				
			||||||
 | 
					                    // onwards so break out
 | 
				
			||||||
 | 
					                    _ => break 'expansion,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            break;
 | 
					            // none of our states have changed so stop the loop
 | 
				
			||||||
 | 
					            break 'expansion;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.fill(&original_file, speculative_file, offset, derive_ctx);
 | 
					        self.fill(&original_file, speculative_file, offset, derive_ctx);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Calculate the expected type and name of the cursor position.
 | 
				
			||||||
    fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
 | 
					    fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
 | 
				
			||||||
        let mut node = match self.token.parent() {
 | 
					        let mut node = match self.token.parent() {
 | 
				
			||||||
            Some(it) => it,
 | 
					            Some(it) => it,
 | 
				
			||||||
@ -734,6 +752,8 @@ impl<'a> CompletionContext<'a> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Fill the completion context, this is what does semantic reasoning about the surrounding context
 | 
				
			||||||
 | 
					    /// of the completion location.
 | 
				
			||||||
    fn fill(
 | 
					    fn fill(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
        original_file: &SyntaxNode,
 | 
					        original_file: &SyntaxNode,
 | 
				
			||||||
@ -1067,6 +1087,7 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Attempts to find `node` inside `syntax` via `node`'s text range.
 | 
				
			||||||
fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
 | 
					fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
 | 
				
			||||||
    let syntax_range = syntax.text_range();
 | 
					    let syntax_range = syntax.text_range();
 | 
				
			||||||
    let range = node.syntax().text_range();
 | 
					    let range = node.syntax().text_range();
 | 
				
			||||||
@ -1074,7 +1095,8 @@ fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
 | 
				
			|||||||
    syntax.covering_element(intersection).ancestors().find_map(N::cast)
 | 
					    syntax.covering_element(intersection).ancestors().find_map(N::cast)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Compensates for the offset introduced by the fake ident
 | 
					/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
 | 
				
			||||||
 | 
					/// for the offset introduced by the fake ident.
 | 
				
			||||||
/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
 | 
					/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
 | 
				
			||||||
fn find_node_in_file_compensated<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
 | 
					fn find_node_in_file_compensated<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
 | 
				
			||||||
    let syntax_range = syntax.text_range();
 | 
					    let syntax_range = syntax.text_range();
 | 
				
			||||||
@ -1143,6 +1165,7 @@ const OP_TRAIT_LANG_NAMES: &[&str] = &[
 | 
				
			|||||||
    "shr",
 | 
					    "shr",
 | 
				
			||||||
    "sub",
 | 
					    "sub",
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use expect_test::{expect, Expect};
 | 
					    use expect_test::{expect, Expect};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user