//! See `CompletionContext` structure.
-use std::iter;
+mod analysis;
+#[cfg(test)]
+mod tests;
use base_db::SourceDatabaseExt;
use hir::{
HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
};
use ide_db::{
- active_parameter::ActiveParameter,
base_db::{FilePosition, SourceDatabase},
famous_defs::FamousDefs,
FxHashMap, FxHashSet, RootDatabase,
};
use syntax::{
- algo::{find_node_at_offset, non_trivia_sibling},
- ast::{self, AttrKind, HasName, NameOrNameRef},
- match_ast, AstNode, NodeOrToken,
+ ast::{self, AttrKind, NameOrNameRef},
+ AstNode,
SyntaxKind::{self, *},
- SyntaxNode, SyntaxToken, TextRange, TextSize, T,
+ SyntaxToken, TextRange, TextSize,
};
use text_edit::Indel;
-use crate::{
- patterns::{
- determine_location, determine_prev_sibling, is_in_loop_body, is_in_token_of_for_loop,
- previous_token, ImmediateLocation, ImmediatePrevSibling,
- },
- CompletionConfig,
-};
+use crate::CompletionConfig;
const COMPLETION_MARKER: &str = "intellijRulezz";
No,
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+/// Existing qualifiers for the thing we are currently completing.
+#[derive(Debug, Default)]
+pub(super) struct QualifierCtx {
+ pub(super) unsafe_tok: Option<SyntaxToken>,
+ pub(super) vis_node: Option<ast::Visibility>,
+}
+
+impl QualifierCtx {
+ pub(super) fn none(&self) -> bool {
+ self.unsafe_tok.is_none() && self.vis_node.is_none()
+ }
+}
+
+/// The state of the path we are currently completing.
+#[derive(Debug)]
+pub(crate) struct PathCompletionCtx {
+ /// If this is a call with () already there (or {} in case of record patterns)
+ pub(super) has_call_parens: bool,
+ /// If this has a macro call bang !
+ pub(super) has_macro_bang: bool,
+ /// The qualifier of the current path.
+ pub(super) qualified: Qualified,
+ /// The parent of the path we are completing.
+ pub(super) parent: Option<ast::Path>,
+ pub(super) kind: PathKind,
+ /// Whether the path segment has type args or not.
+ pub(super) has_type_args: bool,
+ /// Whether the qualifier comes from a use tree parent or not
+ pub(crate) use_tree_parent: bool,
+}
+
+impl PathCompletionCtx {
+ pub(super) fn is_trivial_path(&self) -> bool {
+ matches!(
+ self,
+ PathCompletionCtx {
+ has_call_parens: false,
+ has_macro_bang: false,
+ qualified: Qualified::No,
+ parent: None,
+ has_type_args: false,
+ ..
+ }
+ )
+ }
+}
+
+/// The kind of path we are completing right now.
+#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum PathKind {
- Expr,
- Type,
+ Expr {
+ in_block_expr: bool,
+ in_loop_body: bool,
+ after_if_expr: bool,
+ /// Whether this expression is the direct condition of an if or while expression
+ in_condition: bool,
+ ref_expr_parent: Option<ast::RefExpr>,
+ is_func_update: Option<ast::RecordExpr>,
+ },
+ Type {
+ location: TypeLocation,
+ },
Attr {
kind: AttrKind,
annotated_item_kind: Option<SyntaxKind>,
},
- Derive,
+ Derive {
+ existing_derives: FxHashSet<hir::Macro>,
+ },
/// Path in item position, that is inside an (Assoc)ItemList
- Item,
+ Item {
+ kind: ItemListKind,
+ },
Pat,
Vis {
has_in_token: bool,
Use,
}
-#[derive(Debug)]
-pub(crate) struct PathCompletionCtx {
- /// If this is a call with () already there (or {} in case of record patterns)
- pub(super) has_call_parens: bool,
- /// If this has a macro call bang !
- pub(super) has_macro_bang: bool,
- /// Whether this path stars with a `::`.
- pub(super) is_absolute_path: bool,
- /// The qualifier of the current path if it exists.
- pub(super) qualifier: Option<PathQualifierCtx>,
- pub(super) kind: Option<PathKind>,
- /// Whether the path segment has type args or not.
- pub(super) has_type_args: bool,
- /// `true` if we are a statement or a last expr in the block.
- pub(super) can_be_stmt: bool,
- pub(super) in_loop_body: bool,
+/// Original file ast nodes
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum TypeLocation {
+ TupleField,
+ TypeAscription(TypeAscriptionTarget),
+ GenericArgList(Option<ast::GenericArgList>),
+ TypeBound,
+ ImplTarget,
+ ImplTrait,
+ Other,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum TypeAscriptionTarget {
+ Let(Option<ast::Pat>),
+ FnParam(Option<ast::Pat>),
+ RetType(Option<ast::Expr>),
+ Const(Option<ast::Expr>),
+}
+
+/// The kind of item list a [`PathKind::Item`] belongs to.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(super) enum ItemListKind {
+ SourceFile,
+ Module,
+ Impl,
+ TraitImpl,
+ Trait,
+ ExternBlock,
}
#[derive(Debug)]
-pub(crate) struct PathQualifierCtx {
- pub(crate) path: ast::Path,
- pub(crate) resolution: Option<PathResolution>,
- /// Whether this path consists solely of `super` segments
- pub(crate) is_super_chain: bool,
- /// Whether the qualifier comes from a use tree parent or not
- pub(crate) use_tree_parent: bool,
+pub(super) enum Qualified {
+ No,
+ With {
+ path: ast::Path,
+ resolution: Option<PathResolution>,
+ /// Whether this path consists solely of `super` segments
+ is_super_chain: bool,
+ },
+ /// <_>::
+ Infer,
+ /// Whether the path is an absolute path
+ Absolute,
}
+/// The state of the pattern we are completing.
#[derive(Debug)]
pub(super) struct PatternContext {
pub(super) refutability: PatternRefutability,
pub(super) parent_pat: Option<ast::Pat>,
pub(super) ref_token: Option<SyntaxToken>,
pub(super) mut_token: Option<SyntaxToken>,
+ /// The record pattern this name or ref is a field of
+ pub(super) record_pat: Option<ast::RecordPat>,
}
+/// The state of the lifetime we are completing.
#[derive(Debug)]
-pub(super) enum LifetimeContext {
+pub(super) struct LifetimeContext {
+ pub(super) lifetime: Option<ast::Lifetime>,
+ pub(super) kind: LifetimeKind,
+}
+
+/// The kind of lifetime we are completing.
+#[derive(Debug)]
+pub(super) enum LifetimeKind {
LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
Lifetime,
LabelRef,
LabelDef,
}
+/// The state of the name we are completing.
+#[derive(Debug)]
+pub(super) struct NameContext {
+ #[allow(dead_code)]
+ pub(super) name: Option<ast::Name>,
+ pub(super) kind: NameKind,
+}
+
+/// The kind of the name we are completing.
#[derive(Debug)]
#[allow(dead_code)]
-pub(super) enum NameContext {
+pub(super) enum NameKind {
Const,
ConstParam,
Enum,
Variant,
}
+/// The state of the NameRef we are completing.
+#[derive(Debug)]
+pub(super) struct NameRefContext {
+ /// NameRef syntax in the original file
+ pub(super) nameref: Option<ast::NameRef>,
+ // FIXME: This shouldn't be an Option
+ pub(super) kind: Option<NameRefKind>,
+}
+
+/// The kind of the NameRef we are completing.
+#[derive(Debug)]
+pub(super) enum NameRefKind {
+ Path(PathCompletionCtx),
+ DotAccess(DotAccess),
+ /// Position where we are only interested in keyword completions
+ Keyword(ast::Item),
+ /// The record expression this nameref is a field of
+ RecordExpr(ast::RecordExpr),
+}
+
+/// The identifier we are currently completing.
+#[derive(Debug)]
+pub(super) enum IdentContext {
+ Name(NameContext),
+ NameRef(NameRefContext),
+ Lifetime(LifetimeContext),
+ /// The string the cursor is currently inside
+ String {
+ /// original token
+ original: ast::String,
+ /// fake token
+ expanded: Option<ast::String>,
+ },
+ /// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)`
+ UnexpandedAttrTT {
+ fake_attribute_under_caret: Option<ast::Attr>,
+ },
+}
+
+/// Information about the field or method access we are completing.
+#[derive(Debug)]
+pub(super) struct DotAccess {
+ pub(super) receiver: Option<ast::Expr>,
+ pub(super) receiver_ty: Option<TypeInfo>,
+ pub(super) kind: DotAccessKind,
+}
+
+#[derive(Debug)]
+pub(super) enum DotAccessKind {
+ Field {
+ /// True if the receiver is an integer and there is no ident in the original file after it yet
+ /// like `0.$0`
+ receiver_is_ambiguous_float_literal: bool,
+ },
+ Method {
+ has_parens: bool,
+ },
+}
+
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ParamKind {
Function(ast::Fn),
pub(super) expected_type: Option<Type>,
/// The parent function of the cursor position if it exists.
+ // FIXME: This probably doesn't belong here
pub(super) function_def: Option<ast::Fn>,
/// The parent impl of the cursor position if it exists.
+ // FIXME: This probably doesn't belong here
pub(super) impl_def: Option<ast::Impl>,
- /// The NameLike under the cursor in the original file if it exists.
- pub(super) name_syntax: Option<ast::NameLike>,
/// Are we completing inside a let statement with a missing semicolon?
+ // FIXME: This should be part of PathKind::Expr
pub(super) incomplete_let: bool,
- pub(super) completion_location: Option<ImmediateLocation>,
- pub(super) prev_sibling: Option<ImmediatePrevSibling>,
- pub(super) fake_attribute_under_caret: Option<ast::Attr>,
+ // FIXME: This shouldn't exist
pub(super) previous_token: Option<SyntaxToken>,
- pub(super) name_ctx: Option<NameContext>,
- pub(super) lifetime_ctx: Option<LifetimeContext>,
+ // We might wanna split these out of CompletionContext
+ pub(super) ident_ctx: IdentContext,
pub(super) pattern_ctx: Option<PatternContext>,
- pub(super) path_context: Option<PathCompletionCtx>,
-
- pub(super) existing_derives: FxHashSet<hir::Macro>,
+ pub(super) qualifier_ctx: QualifierCtx,
pub(super) locals: FxHashMap<Name, Local>,
}
}
}
- pub(crate) fn name_ref(&self) -> Option<&ast::NameRef> {
- self.name_syntax.as_ref().and_then(ast::NameLike::as_name_ref)
- }
-
- pub(crate) fn lifetime(&self) -> Option<&ast::Lifetime> {
- self.name_syntax.as_ref().and_then(ast::NameLike::as_lifetime)
- }
-
+ // FIXME: This shouldn't exist
pub(crate) fn previous_token_is(&self, kind: SyntaxKind) -> bool {
self.previous_token.as_ref().map_or(false, |tok| tok.kind() == kind)
}
FamousDefs(&self.sema, self.krate)
}
- pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> {
- match &self.completion_location {
- Some(
- ImmediateLocation::MethodCall { receiver, .. }
- | ImmediateLocation::FieldAccess { receiver, .. },
- ) => receiver.as_ref(),
- _ => None,
- }
- }
-
- pub(crate) fn has_dot_receiver(&self) -> bool {
- matches!(
- &self.completion_location,
- Some(ImmediateLocation::FieldAccess { receiver, .. } | ImmediateLocation::MethodCall { receiver,.. })
- if receiver.is_some()
- )
- }
-
- pub(crate) fn expects_assoc_item(&self) -> bool {
- matches!(self.completion_location, Some(ImmediateLocation::Trait | ImmediateLocation::Impl))
- }
-
- pub(crate) fn expects_variant(&self) -> bool {
- matches!(self.name_ctx, Some(NameContext::Variant))
- }
-
- pub(crate) fn expects_non_trait_assoc_item(&self) -> bool {
- matches!(self.completion_location, Some(ImmediateLocation::Impl))
- }
-
- pub(crate) fn expects_item(&self) -> bool {
- matches!(self.completion_location, Some(ImmediateLocation::ItemList))
- }
-
- pub(crate) fn expects_generic_arg(&self) -> bool {
- matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_)))
- }
-
- pub(crate) fn has_block_expr_parent(&self) -> bool {
- matches!(self.completion_location, Some(ImmediateLocation::StmtList))
- }
-
- pub(crate) fn expects_ident_ref_expr(&self) -> bool {
- matches!(self.completion_location, Some(ImmediateLocation::RefExpr))
- }
-
- pub(crate) fn expect_field(&self) -> bool {
- matches!(self.completion_location, Some(ImmediateLocation::TupleField))
- || matches!(self.name_ctx, Some(NameContext::RecordField))
- }
-
- /// Whether the cursor is right after a trait or impl header.
- /// trait Foo ident$0
- // FIXME: This probably shouldn't exist
- pub(crate) fn has_unfinished_impl_or_trait_prev_sibling(&self) -> bool {
- matches!(
- self.prev_sibling,
- Some(ImmediatePrevSibling::ImplDefType | ImmediatePrevSibling::TraitDefName)
- )
- }
-
- // FIXME: This probably shouldn't exist
- pub(crate) fn has_impl_prev_sibling(&self) -> bool {
- matches!(self.prev_sibling, Some(ImmediatePrevSibling::ImplDefType))
- }
-
- pub(crate) fn has_visibility_prev_sibling(&self) -> bool {
- matches!(self.prev_sibling, Some(ImmediatePrevSibling::Visibility))
- }
-
- pub(crate) fn after_if(&self) -> bool {
- matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr))
- }
-
- // FIXME: This shouldn't exist
- pub(crate) fn is_path_disallowed(&self) -> bool {
- self.previous_token_is(T![unsafe])
- || matches!(
- self.prev_sibling,
- Some(ImmediatePrevSibling::Attribute | ImmediatePrevSibling::Visibility)
- )
- || matches!(
- self.completion_location,
- Some(ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_))
- )
- || matches!(self.name_ctx, Some(NameContext::Module(_) | NameContext::Rename))
- }
-
- pub(crate) fn expects_expression(&self) -> bool {
- matches!(self.path_context, Some(PathCompletionCtx { kind: Some(PathKind::Expr), .. }))
- }
-
- pub(crate) fn expects_type(&self) -> bool {
- matches!(self.path_context, Some(PathCompletionCtx { kind: Some(PathKind::Type), .. }))
- }
-
- pub(crate) fn path_is_call(&self) -> bool {
- self.path_context.as_ref().map_or(false, |it| it.has_call_parens)
- }
-
- pub(crate) fn is_non_trivial_path(&self) -> bool {
- matches!(
- self.path_context,
- Some(
- PathCompletionCtx { is_absolute_path: true, .. }
- | PathCompletionCtx { qualifier: Some(_), .. }
- )
- )
- }
-
- pub(crate) fn path_qual(&self) -> Option<&ast::Path> {
- self.path_context.as_ref().and_then(|it| it.qualifier.as_ref().map(|it| &it.path))
- }
-
- pub(crate) fn path_kind(&self) -> Option<PathKind> {
- self.path_context.as_ref().and_then(|it| it.kind)
- }
-
- pub(crate) fn is_immediately_after_macro_bang(&self) -> bool {
- self.token.kind() == BANG && self.token.parent().map_or(false, |it| it.kind() == MACRO_CALL)
- }
-
/// Checks if an item is visible and not `doc(hidden)` at the completion site.
pub(crate) fn is_visible<I>(&self, item: &I) -> Visible
where
}
}
+ /// Returns the traits in scope, with the [`Drop`] trait removed.
+ pub(crate) fn traits_in_scope(&self) -> hir::VisibleTraits {
+ let mut traits_in_scope = self.scope.visible_traits();
+ if let Some(drop) = self.famous_defs().core_ops_Drop() {
+ traits_in_scope.0.remove(&drop.into());
+ }
+ traits_in_scope
+ }
+
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
let _p = profile::span("CompletionContext::process_all_names");
expected_type: None,
function_def: None,
impl_def: None,
- name_syntax: None,
- lifetime_ctx: None,
- pattern_ctx: None,
- name_ctx: None,
- completion_location: None,
- prev_sibling: None,
- fake_attribute_under_caret: None,
+ incomplete_let: false,
previous_token: None,
- path_context: None,
+ // dummy value, will be overwritten
+ ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None },
+ pattern_ctx: None,
+ qualifier_ctx: Default::default(),
locals,
- incomplete_let: false,
- existing_derives: Default::default(),
};
ctx.expand_and_fill(
original_file.syntax().clone(),
file_with_fake_ident.syntax().clone(),
offset,
fake_ident_token,
- );
+ )?;
Some(ctx)
}
-
- /// 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_and_fill(
- &mut self,
- mut original_file: SyntaxNode,
- mut speculative_file: SyntaxNode,
- mut offset: TextSize,
- mut fake_ident_token: SyntaxToken,
- ) {
- 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::<ast::Item>(&original_file, offset),
- find_node_at_offset::<ast::Item>(&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 (
- 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;
- }
- // 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::<ast::TokenTree>(&original_file, offset) {
- Some(it) => it,
- None => break 'expansion,
- };
- let spec_tt = match find_node_at_offset::<ast::TokenTree>(&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()),
- ) {
- 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
- 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,
- };
-
- 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;
- }
- // 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.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>) {
- let mut node = match self.token.parent() {
- Some(it) => it,
- None => return (None, 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 = if has_ref(&self.token) {
- cov_mark::hit!(expected_type_fn_param_ref);
- ap.ty.remove_ref()
- } else {
- Some(ap.ty)
- };
- (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),
- )
- }
- },
- ast::MatchExpr(it) => {
- cov_mark::hit!(expected_type_match_arm_without_leading_char);
- let ty = 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),
- }
- },
- }
- };
- }
- }
-
- /// Fill the completion context, this is what does semantic reasoning about the surrounding context
- /// of the completion location.
- fn fill(
- &mut self,
- original_file: &SyntaxNode,
- file_with_fake_ident: SyntaxNode,
- offset: TextSize,
- derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>,
- ) {
- let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
- 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;
- }
-
- self.previous_token = previous_token(syntax_element.clone());
- self.fake_attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast);
-
- self.incomplete_let =
- syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| {
- it.syntax().text_range().end() == syntax_element.text_range().end()
- });
-
- (self.expected_type, self.expected_name) = self.expected_type_and_name();
-
- // Overwrite the path kind for derives
- if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
- self.existing_derives = self
- .sema
- .resolve_derive_macro(&origin_attr)
- .into_iter()
- .flatten()
- .flatten()
- .collect();
-
- if let Some(ast::NameLike::NameRef(name_ref)) =
- find_node_at_offset(&file_with_fake_ident, offset)
- {
- self.name_syntax =
- find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
- if let Some((path_ctx, _)) =
- Self::classify_name_ref(&self.sema, &original_file, name_ref)
- {
- self.path_context =
- Some(PathCompletionCtx { kind: Some(PathKind::Derive), ..path_ctx });
- }
- }
- return;
- }
-
- let name_like = match find_node_at_offset(&file_with_fake_ident, offset) {
- Some(it) => it,
- None => return,
- };
- self.completion_location =
- determine_location(&self.sema, original_file, offset, &name_like);
- self.prev_sibling = determine_prev_sibling(&name_like);
- self.name_syntax =
- find_node_at_offset(original_file, name_like.syntax().text_range().start());
- self.impl_def = self
- .sema
- .token_ancestors_with_macros(self.token.clone())
- .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
- .find_map(ast::Impl::cast);
- self.function_def = self
- .sema
- .token_ancestors_with_macros(self.token.clone())
- .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
- .find_map(ast::Fn::cast);
-
- match name_like {
- ast::NameLike::Lifetime(lifetime) => {
- self.lifetime_ctx = Self::classify_lifetime(&self.sema, original_file, lifetime);
- }
- ast::NameLike::NameRef(name_ref) => {
- if let Some((path_ctx, pat_ctx)) =
- Self::classify_name_ref(&self.sema, original_file, name_ref)
- {
- self.path_context = Some(path_ctx);
- self.pattern_ctx = pat_ctx;
- }
- }
- ast::NameLike::Name(name) => {
- if let Some((name_ctx, pat_ctx)) =
- Self::classify_name(&self.sema, original_file, name)
- {
- self.pattern_ctx = pat_ctx;
- self.name_ctx = Some(name_ctx);
- }
- }
- }
- }
-
- fn classify_lifetime(
- _sema: &Semantics<RootDatabase>,
- _original_file: &SyntaxNode,
- lifetime: ast::Lifetime,
- ) -> Option<LifetimeContext> {
- let parent = lifetime.syntax().parent()?;
- if parent.kind() == ERROR {
- return None;
- }
-
- Some(match_ast! {
- match parent {
- ast::LifetimeParam(param) => LifetimeContext::LifetimeParam {
- is_decl: param.lifetime().as_ref() == Some(&lifetime),
- param
- },
- ast::BreakExpr(_) => LifetimeContext::LabelRef,
- ast::ContinueExpr(_) => LifetimeContext::LabelRef,
- ast::Label(_) => LifetimeContext::LabelDef,
- _ => LifetimeContext::Lifetime,
- }
- })
- }
-
- fn classify_name(
- _sema: &Semantics<RootDatabase>,
- original_file: &SyntaxNode,
- name: ast::Name,
- ) -> Option<(NameContext, Option<PatternContext>)> {
- let parent = name.syntax().parent()?;
- let mut pat_ctx = None;
- let name_ctx = match_ast! {
- match parent {
- ast::Const(_) => NameContext::Const,
- ast::ConstParam(_) => NameContext::ConstParam,
- ast::Enum(_) => NameContext::Enum,
- ast::Fn(_) => NameContext::Function,
- ast::IdentPat(bind_pat) => {
- let is_name_in_field_pat = bind_pat
- .syntax()
- .parent()
- .and_then(ast::RecordPatField::cast)
- .map_or(false, |pat_field| pat_field.name_ref().is_none());
- if !is_name_in_field_pat {
- pat_ctx = Some(pattern_context_for(original_file, bind_pat.into()));
- }
-
- NameContext::IdentPat
- },
- ast::MacroDef(_) => NameContext::MacroDef,
- ast::MacroRules(_) => NameContext::MacroRules,
- ast::Module(module) => NameContext::Module(module),
- ast::RecordField(_) => NameContext::RecordField,
- ast::Rename(_) => NameContext::Rename,
- ast::SelfParam(_) => NameContext::SelfParam,
- ast::Static(_) => NameContext::Static,
- ast::Struct(_) => NameContext::Struct,
- ast::Trait(_) => NameContext::Trait,
- ast::TypeAlias(_) => NameContext::TypeAlias,
- ast::TypeParam(_) => NameContext::TypeParam,
- ast::Union(_) => NameContext::Union,
- ast::Variant(_) => NameContext::Variant,
- _ => return None,
- }
- };
- Some((name_ctx, pat_ctx))
- }
-
- fn classify_name_ref(
- sema: &Semantics<RootDatabase>,
- original_file: &SyntaxNode,
- name_ref: ast::NameRef,
- ) -> Option<(PathCompletionCtx, Option<PatternContext>)> {
- let parent = name_ref.syntax().parent()?;
- let segment = ast::PathSegment::cast(parent)?;
- let path = segment.parent_path();
-
- let mut path_ctx = PathCompletionCtx {
- has_call_parens: false,
- is_absolute_path: false,
- qualifier: None,
- has_type_args: false,
- can_be_stmt: false,
- in_loop_body: false,
- has_macro_bang: false,
- kind: None,
- };
- let mut pat_ctx = None;
- path_ctx.in_loop_body = is_in_loop_body(name_ref.syntax());
-
- path_ctx.kind = path.syntax().ancestors().find_map(|it| {
- // using Option<Option<PathKind>> as extra controlflow
- let kind = match_ast! {
- match it {
- ast::PathType(_) => Some(PathKind::Type),
- ast::PathExpr(it) => {
- path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
- Some(PathKind::Expr)
- },
- ast::TupleStructPat(it) => {
- path_ctx.has_call_parens = true;
- pat_ctx = Some(pattern_context_for(original_file, it.into()));
- Some(PathKind::Pat)
- },
- ast::RecordPat(it) => {
- path_ctx.has_call_parens = true;
- pat_ctx = Some(pattern_context_for(original_file, it.into()));
- Some(PathKind::Pat)
- },
- ast::PathPat(it) => {
- pat_ctx = Some(pattern_context_for(original_file, it.into()));
- Some(PathKind::Pat)
- },
- ast::MacroCall(it) => {
- path_ctx.has_macro_bang = it.excl_token().is_some();
- match it.syntax().parent().map(|it| it.kind()) {
- Some(SyntaxKind::MACRO_PAT) => Some(PathKind::Pat),
- Some(SyntaxKind::MACRO_TYPE) => Some(PathKind::Type),
- Some(SyntaxKind::MACRO_EXPR) => Some(PathKind::Expr),
- Some(
- SyntaxKind::ITEM_LIST
- | SyntaxKind::ASSOC_ITEM_LIST
- | SyntaxKind::EXTERN_ITEM_LIST
- | SyntaxKind::SOURCE_FILE
- ) => Some(PathKind::Item),
- _ => return Some(None),
- }
- },
- ast::Meta(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 {
- kind,
- annotated_item_kind,
- })
- })(),
- ast::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }),
- ast::UseTree(_) => Some(PathKind::Use),
- ast::ItemList(_) => Some(PathKind::Item),
- ast::AssocItemList(_) => Some(PathKind::Item),
- ast::ExternItemList(_) => Some(PathKind::Item),
- ast::SourceFile(_) => Some(PathKind::Item),
- _ => return None,
- }
- };
- Some(kind)
- }).flatten();
- path_ctx.has_type_args = segment.generic_arg_list().is_some();
-
- if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
- if !use_tree_parent {
- path_ctx.is_absolute_path =
- path.top_path().segment().map_or(false, |it| it.coloncolon_token().is_some());
- }
-
- let path = path
- .segment()
- .and_then(|it| find_node_in_file(original_file, &it))
- .map(|it| it.parent_path());
- path_ctx.qualifier = path.map(|path| {
- let res = sema.resolve_path(&path);
- let is_super_chain = iter::successors(Some(path.clone()), |p| p.qualifier())
- .all(|p| p.segment().and_then(|s| s.super_token()).is_some());
- PathQualifierCtx { path, resolution: res, is_super_chain, use_tree_parent }
- });
- return Some((path_ctx, pat_ctx));
- }
-
- if let Some(segment) = path.segment() {
- if segment.coloncolon_token().is_some() {
- path_ctx.is_absolute_path = true;
- return Some((path_ctx, pat_ctx));
- }
- }
-
- // Find either enclosing expr statement (thing with `;`) or a
- // block. If block, check that we are the last expr.
- path_ctx.can_be_stmt = name_ref
- .syntax()
- .ancestors()
- .find_map(|node| {
- if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
- return Some(stmt.syntax().text_range() == name_ref.syntax().text_range());
- }
- if let Some(stmt_list) = ast::StmtList::cast(node) {
- return Some(
- stmt_list.tail_expr().map(|e| e.syntax().text_range())
- == Some(name_ref.syntax().text_range()),
- );
- }
- None
- })
- .unwrap_or(false);
- Some((path_ctx, pat_ctx))
- }
-}
-
-fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternContext {
- let mut is_param = None;
- let (refutability, has_type_ascription) =
- pat
- .syntax()
- .ancestors()
- .skip_while(|it| ast::Pat::can_cast(it.kind()))
- .next()
- .map_or((PatternRefutability::Irrefutable, false), |node| {
- let refutability = match_ast! {
- match node {
- ast::LetStmt(let_) => return (PatternRefutability::Irrefutable, let_.ty().is_some()),
- ast::Param(param) => {
- let has_type_ascription = param.ty().is_some();
- is_param = (|| {
- let fake_param_list = param.syntax().parent().and_then(ast::ParamList::cast)?;
- let param_list = find_node_in_file_compensated(original_file, &fake_param_list)?;
- let param_list_owner = param_list.syntax().parent()?;
- let kind = match_ast! {
- match param_list_owner {
- ast::ClosureExpr(closure) => ParamKind::Closure(closure),
- ast::Fn(fn_) => ParamKind::Function(fn_),
- _ => return None,
- }
- };
- Some((param_list, param, kind))
- })();
- return (PatternRefutability::Irrefutable, has_type_ascription)
- },
- ast::MatchArm(_) => PatternRefutability::Refutable,
- ast::LetExpr(_) => PatternRefutability::Refutable,
- ast::ForExpr(_) => PatternRefutability::Irrefutable,
- _ => PatternRefutability::Irrefutable,
- }
- };
- (refutability, false)
- });
- let (ref_token, mut_token) = match &pat {
- ast::Pat::IdentPat(it) => (it.ref_token(), it.mut_token()),
- _ => (None, None),
- };
- PatternContext {
- refutability,
- param_ctx: is_param,
- has_type_ascription,
- parent_pat: pat.syntax().parent().and_then(ast::Pat::cast),
- mut_token,
- ref_token,
- }
-}
-
-/// Attempts to find `node` inside `syntax` via `node`'s text range.
-fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
- let syntax_range = syntax.text_range();
- let range = node.syntax().text_range();
- let intersection = range.intersect(syntax_range)?;
- syntax.covering_element(intersection).ancestors().find_map(N::cast)
-}
-
-/// 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.
-fn find_node_in_file_compensated<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
- let syntax_range = syntax.text_range();
- let range = node.syntax().text_range();
- let end = range.end().checked_sub(TextSize::try_from(COMPLETION_MARKER.len()).ok()?)?;
- if end < range.start() {
- return None;
- }
- let range = TextRange::new(range.start(), end);
- // our inserted ident could cause `range` to be go outside of the original syntax, so cap it
- let intersection = range.intersect(syntax_range)?;
- syntax.covering_element(intersection).ancestors().find_map(N::cast)
-}
-
-fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
- if let Some(qual) = path.qualifier() {
- return Some((qual, false));
- }
- let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?;
- let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?;
- Some((use_tree.path()?, true))
-}
-
-fn has_ref(token: &SyntaxToken) -> bool {
- let mut token = token.clone();
- for skip in [IDENT, WHITESPACE, T![mut]] {
- if token.kind() == skip {
- token = match token.prev_token() {
- Some(it) => it,
- None => return false,
- }
- }
- }
- token.kind() == T![&]
}
const OP_TRAIT_LANG_NAMES: &[&str] = &[
"shr",
"sub",
];
-
-#[cfg(test)]
-mod tests {
- use expect_test::{expect, Expect};
- use hir::HirDisplay;
-
- use crate::tests::{position, TEST_CONFIG};
-
- use super::CompletionContext;
-
- fn check_expected_type_and_name(ra_fixture: &str, expect: Expect) {
- let (db, pos) = position(ra_fixture);
- let config = TEST_CONFIG;
- let completion_context = CompletionContext::new(&db, pos, &config).unwrap();
-
- let ty = completion_context
- .expected_type
- .map(|t| t.display_test(&db).to_string())
- .unwrap_or("?".to_owned());
-
- let name = completion_context
- .expected_name
- .map_or_else(|| "?".to_owned(), |name| name.to_string());
-
- expect.assert_eq(&format!("ty: {}, name: {}", ty, name));
- }
-
- #[test]
- fn expected_type_let_without_leading_char() {
- cov_mark::check!(expected_type_let_without_leading_char);
- check_expected_type_and_name(
- r#"
-fn foo() {
- let x: u32 = $0;
-}
-"#,
- expect![[r#"ty: u32, name: x"#]],
- );
- }
-
- #[test]
- fn expected_type_let_with_leading_char() {
- cov_mark::check!(expected_type_let_with_leading_char);
- check_expected_type_and_name(
- r#"
-fn foo() {
- let x: u32 = c$0;
-}
-"#,
- expect![[r#"ty: u32, name: x"#]],
- );
- }
-
- #[test]
- fn expected_type_let_pat() {
- check_expected_type_and_name(
- r#"
-fn foo() {
- let x$0 = 0u32;
-}
-"#,
- expect![[r#"ty: u32, name: ?"#]],
- );
- check_expected_type_and_name(
- r#"
-fn foo() {
- let $0 = 0u32;
-}
-"#,
- expect![[r#"ty: u32, name: ?"#]],
- );
- }
-
- #[test]
- fn expected_type_fn_param() {
- cov_mark::check!(expected_type_fn_param);
- check_expected_type_and_name(
- r#"
-fn foo() { bar($0); }
-fn bar(x: u32) {}
-"#,
- expect![[r#"ty: u32, name: x"#]],
- );
- check_expected_type_and_name(
- r#"
-fn foo() { bar(c$0); }
-fn bar(x: u32) {}
-"#,
- expect![[r#"ty: u32, name: x"#]],
- );
- }
-
- #[test]
- fn expected_type_fn_param_ref() {
- cov_mark::check!(expected_type_fn_param_ref);
- check_expected_type_and_name(
- r#"
-fn foo() { bar(&$0); }
-fn bar(x: &u32) {}
-"#,
- expect![[r#"ty: u32, name: x"#]],
- );
- check_expected_type_and_name(
- r#"
-fn foo() { bar(&mut $0); }
-fn bar(x: &mut u32) {}
-"#,
- expect![[r#"ty: u32, name: x"#]],
- );
- check_expected_type_and_name(
- r#"
-fn foo() { bar(& c$0); }
-fn bar(x: &u32) {}
- "#,
- expect![[r#"ty: u32, name: x"#]],
- );
- check_expected_type_and_name(
- r#"
-fn foo() { bar(&mut c$0); }
-fn bar(x: &mut u32) {}
-"#,
- expect![[r#"ty: u32, name: x"#]],
- );
- check_expected_type_and_name(
- r#"
-fn foo() { bar(&c$0); }
-fn bar(x: &u32) {}
- "#,
- expect![[r#"ty: u32, name: x"#]],
- );
- }
-
- #[test]
- fn expected_type_struct_field_without_leading_char() {
- cov_mark::check!(expected_type_struct_field_without_leading_char);
- check_expected_type_and_name(
- r#"
-struct Foo { a: u32 }
-fn foo() {
- Foo { a: $0 };
-}
-"#,
- expect![[r#"ty: u32, name: a"#]],
- )
- }
-
- #[test]
- fn expected_type_struct_field_followed_by_comma() {
- cov_mark::check!(expected_type_struct_field_followed_by_comma);
- check_expected_type_and_name(
- r#"
-struct Foo { a: u32 }
-fn foo() {
- Foo { a: $0, };
-}
-"#,
- expect![[r#"ty: u32, name: a"#]],
- )
- }
-
- #[test]
- fn expected_type_generic_struct_field() {
- check_expected_type_and_name(
- r#"
-struct Foo<T> { a: T }
-fn foo() -> Foo<u32> {
- Foo { a: $0 }
-}
-"#,
- expect![[r#"ty: u32, name: a"#]],
- )
- }
-
- #[test]
- fn expected_type_struct_field_with_leading_char() {
- cov_mark::check!(expected_type_struct_field_with_leading_char);
- check_expected_type_and_name(
- r#"
-struct Foo { a: u32 }
-fn foo() {
- Foo { a: c$0 };
-}
-"#,
- expect![[r#"ty: u32, name: a"#]],
- );
- }
-
- #[test]
- fn expected_type_match_arm_without_leading_char() {
- cov_mark::check!(expected_type_match_arm_without_leading_char);
- check_expected_type_and_name(
- r#"
-enum E { X }
-fn foo() {
- match E::X { $0 }
-}
-"#,
- expect![[r#"ty: E, name: ?"#]],
- );
- }
-
- #[test]
- fn expected_type_match_arm_with_leading_char() {
- cov_mark::check!(expected_type_match_arm_with_leading_char);
- check_expected_type_and_name(
- r#"
-enum E { X }
-fn foo() {
- match E::X { c$0 }
-}
-"#,
- expect![[r#"ty: E, name: ?"#]],
- );
- }
-
- #[test]
- fn expected_type_if_let_without_leading_char() {
- cov_mark::check!(expected_type_if_let_without_leading_char);
- check_expected_type_and_name(
- r#"
-enum Foo { Bar, Baz, Quux }
-
-fn foo() {
- let f = Foo::Quux;
- if let $0 = f { }
-}
-"#,
- expect![[r#"ty: Foo, name: ?"#]],
- )
- }
-
- #[test]
- fn expected_type_if_let_with_leading_char() {
- cov_mark::check!(expected_type_if_let_with_leading_char);
- check_expected_type_and_name(
- r#"
-enum Foo { Bar, Baz, Quux }
-
-fn foo() {
- let f = Foo::Quux;
- if let c$0 = f { }
-}
-"#,
- expect![[r#"ty: Foo, name: ?"#]],
- )
- }
-
- #[test]
- fn expected_type_fn_ret_without_leading_char() {
- cov_mark::check!(expected_type_fn_ret_without_leading_char);
- check_expected_type_and_name(
- r#"
-fn foo() -> u32 {
- $0
-}
-"#,
- expect![[r#"ty: u32, name: ?"#]],
- )
- }
-
- #[test]
- fn expected_type_fn_ret_with_leading_char() {
- cov_mark::check!(expected_type_fn_ret_with_leading_char);
- check_expected_type_and_name(
- r#"
-fn foo() -> u32 {
- c$0
-}
-"#,
- expect![[r#"ty: u32, name: ?"#]],
- )
- }
-
- #[test]
- fn expected_type_fn_ret_fn_ref_fully_typed() {
- check_expected_type_and_name(
- r#"
-fn foo() -> u32 {
- foo$0
-}
-"#,
- expect![[r#"ty: u32, name: ?"#]],
- )
- }
-
- #[test]
- fn expected_type_closure_param_return() {
- // FIXME: make this work with `|| $0`
- check_expected_type_and_name(
- r#"
-//- minicore: fn
-fn foo() {
- bar(|| a$0);
-}
-
-fn bar(f: impl FnOnce() -> u32) {}
-"#,
- expect![[r#"ty: u32, name: ?"#]],
- );
- }
-
- #[test]
- fn expected_type_generic_function() {
- check_expected_type_and_name(
- r#"
-fn foo() {
- bar::<u32>($0);
-}
-
-fn bar<T>(t: T) {}
-"#,
- expect![[r#"ty: u32, name: t"#]],
- );
- }
-
- #[test]
- fn expected_type_generic_method() {
- check_expected_type_and_name(
- r#"
-fn foo() {
- S(1u32).bar($0);
-}
-
-struct S<T>(T);
-impl<T> S<T> {
- fn bar(self, t: T) {}
-}
-"#,
- expect![[r#"ty: u32, name: t"#]],
- );
- }
-
- #[test]
- fn expected_type_functional_update() {
- cov_mark::check!(expected_type_struct_func_update);
- check_expected_type_and_name(
- r#"
-struct Foo { field: u32 }
-fn foo() {
- Foo {
- ..$0
- }
-}
-"#,
- expect![[r#"ty: Foo, name: ?"#]],
- );
- }
-
- #[test]
- fn expected_type_param_pat() {
- check_expected_type_and_name(
- r#"
-struct Foo { field: u32 }
-fn foo(a$0: Foo) {}
-"#,
- expect![[r#"ty: Foo, name: ?"#]],
- );
- check_expected_type_and_name(
- r#"
-struct Foo { field: u32 }
-fn foo($0: Foo) {}
-"#,
- // FIXME make this work, currently fails due to pattern recovery eating the `:`
- expect![[r#"ty: ?, name: ?"#]],
- );
- }
-}