| //! This module provides primitives for showing type and function parameter information when editing |
| //! a call or use-site. |
| |
| use std::collections::BTreeSet; |
| |
| use either::Either; |
| use hir::{ |
| AssocItem, DescendPreference, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, |
| Trait, |
| }; |
| use ide_db::{ |
| active_parameter::{callable_for_node, generic_def_for_node}, |
| base_db::FilePosition, |
| documentation::{Documentation, HasDocs}, |
| FxIndexMap, |
| }; |
| use stdx::format_to; |
| use syntax::{ |
| algo, |
| ast::{self, AstChildren, HasArgList}, |
| match_ast, AstNode, Direction, NodeOrToken, SyntaxElementChildren, SyntaxNode, SyntaxToken, |
| TextRange, TextSize, T, |
| }; |
| |
| use crate::RootDatabase; |
| |
| /// Contains information about an item signature as seen from a use site. |
| /// |
| /// This includes the "active parameter", which is the parameter whose value is currently being |
| /// edited. |
| #[derive(Debug)] |
| pub struct SignatureHelp { |
| pub doc: Option<Documentation>, |
| pub signature: String, |
| pub active_parameter: Option<usize>, |
| parameters: Vec<TextRange>, |
| } |
| |
| impl SignatureHelp { |
| pub fn parameter_labels(&self) -> impl Iterator<Item = &str> + '_ { |
| self.parameters.iter().map(move |&it| &self.signature[it]) |
| } |
| |
| pub fn parameter_ranges(&self) -> &[TextRange] { |
| &self.parameters |
| } |
| |
| fn push_call_param(&mut self, param: &str) { |
| self.push_param("(", param); |
| } |
| |
| fn push_generic_param(&mut self, param: &str) { |
| self.push_param("<", param); |
| } |
| |
| fn push_record_field(&mut self, param: &str) { |
| self.push_param("{ ", param); |
| } |
| |
| fn push_param(&mut self, opening_delim: &str, param: &str) { |
| if !self.signature.ends_with(opening_delim) { |
| self.signature.push_str(", "); |
| } |
| let start = TextSize::of(&self.signature); |
| self.signature.push_str(param); |
| let end = TextSize::of(&self.signature); |
| self.parameters.push(TextRange::new(start, end)) |
| } |
| } |
| |
| /// Computes parameter information for the given position. |
| pub(crate) fn signature_help( |
| db: &RootDatabase, |
| FilePosition { file_id, offset }: FilePosition, |
| ) -> Option<SignatureHelp> { |
| let sema = Semantics::new(db); |
| let file = sema.parse(file_id); |
| let file = file.syntax(); |
| let token = file |
| .token_at_offset(offset) |
| .left_biased() |
| // if the cursor is sandwiched between two space tokens and the call is unclosed |
| // this prevents us from leaving the CallExpression |
| .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?; |
| let token = sema.descend_into_macros_single(DescendPreference::None, token); |
| |
| for node in token.parent_ancestors() { |
| match_ast! { |
| match node { |
| ast::ArgList(arg_list) => { |
| let cursor_outside = arg_list.r_paren_token().as_ref() == Some(&token); |
| if cursor_outside { |
| continue; |
| } |
| return signature_help_for_call(&sema, arg_list, token); |
| }, |
| ast::GenericArgList(garg_list) => { |
| let cursor_outside = garg_list.r_angle_token().as_ref() == Some(&token); |
| if cursor_outside { |
| continue; |
| } |
| return signature_help_for_generics(&sema, garg_list, token); |
| }, |
| ast::RecordExpr(record) => { |
| let cursor_outside = record.record_expr_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token); |
| if cursor_outside { |
| continue; |
| } |
| return signature_help_for_record_lit(&sema, record, token); |
| }, |
| ast::RecordPat(record) => { |
| let cursor_outside = record.record_pat_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token); |
| if cursor_outside { |
| continue; |
| } |
| return signature_help_for_record_pat(&sema, record, token); |
| }, |
| ast::TupleStructPat(tuple_pat) => { |
| let cursor_outside = tuple_pat.r_paren_token().as_ref() == Some(&token); |
| if cursor_outside { |
| continue; |
| } |
| return signature_help_for_tuple_struct_pat(&sema, tuple_pat, token); |
| }, |
| ast::TuplePat(tuple_pat) => { |
| let cursor_outside = tuple_pat.r_paren_token().as_ref() == Some(&token); |
| if cursor_outside { |
| continue; |
| } |
| return signature_help_for_tuple_pat(&sema, tuple_pat, token); |
| }, |
| ast::TupleExpr(tuple_expr) => { |
| let cursor_outside = tuple_expr.r_paren_token().as_ref() == Some(&token); |
| if cursor_outside { |
| continue; |
| } |
| return signature_help_for_tuple_expr(&sema, tuple_expr, token); |
| }, |
| _ => (), |
| } |
| } |
| |
| // Stop at multi-line expressions, since the signature of the outer call is not very |
| // helpful inside them. |
| if let Some(expr) = ast::Expr::cast(node.clone()) { |
| if !matches!(expr, ast::Expr::RecordExpr(..)) |
| && expr.syntax().text().contains_char('\n') |
| { |
| break; |
| } |
| } |
| } |
| |
| None |
| } |
| |
| fn signature_help_for_call( |
| sema: &Semantics<'_, RootDatabase>, |
| arg_list: ast::ArgList, |
| token: SyntaxToken, |
| ) -> Option<SignatureHelp> { |
| // Find the calling expression and its NameRef |
| let mut nodes = arg_list.syntax().ancestors().skip(1); |
| let calling_node = loop { |
| if let Some(callable) = ast::CallableExpr::cast(nodes.next()?) { |
| let inside_callable = callable |
| .arg_list() |
| .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start())); |
| if inside_callable { |
| break callable; |
| } |
| } |
| }; |
| |
| let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?; |
| |
| let mut res = |
| SignatureHelp { doc: None, signature: String::new(), parameters: vec![], active_parameter }; |
| |
| let db = sema.db; |
| let mut fn_params = None; |
| match callable.kind() { |
| hir::CallableKind::Function(func) => { |
| res.doc = func.docs(db); |
| format_to!(res.signature, "fn {}", func.name(db).display(db)); |
| fn_params = Some(match callable.receiver_param(db) { |
| Some(_self) => func.params_without_self(db), |
| None => func.assoc_fn_params(db), |
| }); |
| } |
| hir::CallableKind::TupleStruct(strukt) => { |
| res.doc = strukt.docs(db); |
| format_to!(res.signature, "struct {}", strukt.name(db).display(db)); |
| } |
| hir::CallableKind::TupleEnumVariant(variant) => { |
| res.doc = variant.docs(db); |
| format_to!( |
| res.signature, |
| "enum {}::{}", |
| variant.parent_enum(db).name(db).display(db), |
| variant.name(db).display(db) |
| ); |
| } |
| hir::CallableKind::Closure | hir::CallableKind::FnPtr | hir::CallableKind::Other => (), |
| } |
| |
| res.signature.push('('); |
| { |
| if let Some((self_param, _)) = callable.receiver_param(db) { |
| format_to!(res.signature, "{}", self_param.display(db)) |
| } |
| let mut buf = String::new(); |
| for (idx, (pat, ty)) in callable.params(db).into_iter().enumerate() { |
| buf.clear(); |
| if let Some(pat) = pat { |
| match pat { |
| Either::Left(_self) => format_to!(buf, "self: "), |
| Either::Right(pat) => format_to!(buf, "{}: ", pat), |
| } |
| } |
| // APITs (argument position `impl Trait`s) are inferred as {unknown} as the user is |
| // in the middle of entering call arguments. |
| // In that case, fall back to render definitions of the respective parameters. |
| // This is overly conservative: we do not substitute known type vars |
| // (see FIXME in tests::impl_trait) and falling back on any unknowns. |
| match (ty.contains_unknown(), fn_params.as_deref()) { |
| (true, Some(fn_params)) => format_to!(buf, "{}", fn_params[idx].ty().display(db)), |
| _ => format_to!(buf, "{}", ty.display(db)), |
| } |
| res.push_call_param(&buf); |
| } |
| } |
| res.signature.push(')'); |
| |
| let mut render = |ret_type: hir::Type| { |
| if !ret_type.is_unit() { |
| format_to!(res.signature, " -> {}", ret_type.display(db)); |
| } |
| }; |
| match callable.kind() { |
| hir::CallableKind::Function(func) if callable.return_type().contains_unknown() => { |
| render(func.ret_type(db)) |
| } |
| hir::CallableKind::Function(_) |
| | hir::CallableKind::Closure |
| | hir::CallableKind::FnPtr |
| | hir::CallableKind::Other => render(callable.return_type()), |
| hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {} |
| } |
| Some(res) |
| } |
| |
| fn signature_help_for_generics( |
| sema: &Semantics<'_, RootDatabase>, |
| arg_list: ast::GenericArgList, |
| token: SyntaxToken, |
| ) -> Option<SignatureHelp> { |
| let (mut generics_def, mut active_parameter, first_arg_is_non_lifetime) = |
| generic_def_for_node(sema, &arg_list, &token)?; |
| let mut res = SignatureHelp { |
| doc: None, |
| signature: String::new(), |
| parameters: vec![], |
| active_parameter: None, |
| }; |
| |
| let db = sema.db; |
| match generics_def { |
| hir::GenericDef::Function(it) => { |
| res.doc = it.docs(db); |
| format_to!(res.signature, "fn {}", it.name(db).display(db)); |
| } |
| hir::GenericDef::Adt(hir::Adt::Enum(it)) => { |
| res.doc = it.docs(db); |
| format_to!(res.signature, "enum {}", it.name(db).display(db)); |
| } |
| hir::GenericDef::Adt(hir::Adt::Struct(it)) => { |
| res.doc = it.docs(db); |
| format_to!(res.signature, "struct {}", it.name(db).display(db)); |
| } |
| hir::GenericDef::Adt(hir::Adt::Union(it)) => { |
| res.doc = it.docs(db); |
| format_to!(res.signature, "union {}", it.name(db).display(db)); |
| } |
| hir::GenericDef::Trait(it) => { |
| res.doc = it.docs(db); |
| format_to!(res.signature, "trait {}", it.name(db).display(db)); |
| } |
| hir::GenericDef::TraitAlias(it) => { |
| res.doc = it.docs(db); |
| format_to!(res.signature, "trait {}", it.name(db).display(db)); |
| } |
| hir::GenericDef::TypeAlias(it) => { |
| res.doc = it.docs(db); |
| format_to!(res.signature, "type {}", it.name(db).display(db)); |
| } |
| hir::GenericDef::Variant(it) => { |
| // In paths, generics of an enum can be specified *after* one of its variants. |
| // eg. `None::<u8>` |
| // We'll use the signature of the enum, but include the docs of the variant. |
| res.doc = it.docs(db); |
| let enum_ = it.parent_enum(db); |
| format_to!(res.signature, "enum {}", enum_.name(db).display(db)); |
| generics_def = enum_.into(); |
| } |
| // These don't have generic args that can be specified |
| hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None, |
| } |
| |
| let params = generics_def.params(sema.db); |
| let num_lifetime_params = |
| params.iter().take_while(|param| matches!(param, GenericParam::LifetimeParam(_))).count(); |
| if first_arg_is_non_lifetime { |
| // Lifetime parameters were omitted. |
| active_parameter += num_lifetime_params; |
| } |
| res.active_parameter = Some(active_parameter); |
| |
| res.signature.push('<'); |
| let mut buf = String::new(); |
| for param in params { |
| if let hir::GenericParam::TypeParam(ty) = param { |
| if ty.is_implicit(db) { |
| continue; |
| } |
| } |
| |
| buf.clear(); |
| format_to!(buf, "{}", param.display(db)); |
| res.push_generic_param(&buf); |
| } |
| if let hir::GenericDef::Trait(tr) = generics_def { |
| add_assoc_type_bindings(db, &mut res, tr, arg_list); |
| } |
| res.signature.push('>'); |
| |
| Some(res) |
| } |
| |
| fn add_assoc_type_bindings( |
| db: &RootDatabase, |
| res: &mut SignatureHelp, |
| tr: Trait, |
| args: ast::GenericArgList, |
| ) { |
| if args.syntax().ancestors().find_map(ast::TypeBound::cast).is_none() { |
| // Assoc type bindings are only valid in type bound position. |
| return; |
| } |
| |
| let present_bindings = args |
| .generic_args() |
| .filter_map(|arg| match arg { |
| ast::GenericArg::AssocTypeArg(arg) => arg.name_ref().map(|n| n.to_string()), |
| _ => None, |
| }) |
| .collect::<BTreeSet<_>>(); |
| |
| let mut buf = String::new(); |
| for binding in &present_bindings { |
| buf.clear(); |
| format_to!(buf, "{} = …", binding); |
| res.push_generic_param(&buf); |
| } |
| |
| for item in tr.items_with_supertraits(db) { |
| if let AssocItem::TypeAlias(ty) = item { |
| let name = ty.name(db).to_smol_str(); |
| if !present_bindings.contains(&*name) { |
| buf.clear(); |
| format_to!(buf, "{} = …", name); |
| res.push_generic_param(&buf); |
| } |
| } |
| } |
| } |
| |
| fn signature_help_for_record_lit( |
| sema: &Semantics<'_, RootDatabase>, |
| record: ast::RecordExpr, |
| token: SyntaxToken, |
| ) -> Option<SignatureHelp> { |
| signature_help_for_record_( |
| sema, |
| record.record_expr_field_list()?.syntax().children_with_tokens(), |
| &record.path()?, |
| record |
| .record_expr_field_list()? |
| .fields() |
| .filter_map(|field| sema.resolve_record_field(&field)) |
| .map(|(field, _, ty)| (field, ty)), |
| token, |
| ) |
| } |
| |
| fn signature_help_for_record_pat( |
| sema: &Semantics<'_, RootDatabase>, |
| record: ast::RecordPat, |
| token: SyntaxToken, |
| ) -> Option<SignatureHelp> { |
| signature_help_for_record_( |
| sema, |
| record.record_pat_field_list()?.syntax().children_with_tokens(), |
| &record.path()?, |
| record |
| .record_pat_field_list()? |
| .fields() |
| .filter_map(|field| sema.resolve_record_pat_field(&field)), |
| token, |
| ) |
| } |
| |
| fn signature_help_for_tuple_struct_pat( |
| sema: &Semantics<'_, RootDatabase>, |
| pat: ast::TupleStructPat, |
| token: SyntaxToken, |
| ) -> Option<SignatureHelp> { |
| let path = pat.path()?; |
| let path_res = sema.resolve_path(&path)?; |
| let mut res = SignatureHelp { |
| doc: None, |
| signature: String::new(), |
| parameters: vec![], |
| active_parameter: None, |
| }; |
| let db = sema.db; |
| |
| let fields: Vec<_> = if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res { |
| let en = variant.parent_enum(db); |
| |
| res.doc = en.docs(db); |
| format_to!( |
| res.signature, |
| "enum {}::{} (", |
| en.name(db).display(db), |
| variant.name(db).display(db) |
| ); |
| variant.fields(db) |
| } else { |
| let adt = match path_res { |
| PathResolution::SelfType(imp) => imp.self_ty(db).as_adt()?, |
| PathResolution::Def(ModuleDef::Adt(adt)) => adt, |
| _ => return None, |
| }; |
| |
| match adt { |
| hir::Adt::Struct(it) => { |
| res.doc = it.docs(db); |
| format_to!(res.signature, "struct {} (", it.name(db).display(db)); |
| it.fields(db) |
| } |
| _ => return None, |
| } |
| }; |
| Some(signature_help_for_tuple_pat_ish( |
| db, |
| res, |
| pat.syntax(), |
| token, |
| pat.fields(), |
| fields.into_iter().map(|it| it.ty(db)), |
| )) |
| } |
| |
| fn signature_help_for_tuple_pat( |
| sema: &Semantics<'_, RootDatabase>, |
| pat: ast::TuplePat, |
| token: SyntaxToken, |
| ) -> Option<SignatureHelp> { |
| let db = sema.db; |
| let field_pats = pat.fields(); |
| let pat = pat.into(); |
| let ty = sema.type_of_pat(&pat)?; |
| let fields = ty.original.tuple_fields(db); |
| |
| Some(signature_help_for_tuple_pat_ish( |
| db, |
| SignatureHelp { |
| doc: None, |
| signature: String::from('('), |
| parameters: vec![], |
| active_parameter: None, |
| }, |
| pat.syntax(), |
| token, |
| field_pats, |
| fields.into_iter(), |
| )) |
| } |
| |
| fn signature_help_for_tuple_expr( |
| sema: &Semantics<'_, RootDatabase>, |
| expr: ast::TupleExpr, |
| token: SyntaxToken, |
| ) -> Option<SignatureHelp> { |
| let active_parameter = Some( |
| expr.syntax() |
| .children_with_tokens() |
| .filter_map(NodeOrToken::into_token) |
| .filter(|t| t.kind() == T![,]) |
| .take_while(|t| t.text_range().start() <= token.text_range().start()) |
| .count(), |
| ); |
| |
| let db = sema.db; |
| let mut res = SignatureHelp { |
| doc: None, |
| signature: String::from('('), |
| parameters: vec![], |
| active_parameter, |
| }; |
| let expr = sema.type_of_expr(&expr.into())?; |
| let fields = expr.original.tuple_fields(db); |
| let mut buf = String::new(); |
| for ty in fields { |
| format_to!(buf, "{}", ty.display_truncated(db, Some(20))); |
| res.push_call_param(&buf); |
| buf.clear(); |
| } |
| res.signature.push(')'); |
| Some(res) |
| } |
| |
| fn signature_help_for_record_( |
| sema: &Semantics<'_, RootDatabase>, |
| field_list_children: SyntaxElementChildren, |
| path: &ast::Path, |
| fields2: impl Iterator<Item = (hir::Field, hir::Type)>, |
| token: SyntaxToken, |
| ) -> Option<SignatureHelp> { |
| let active_parameter = field_list_children |
| .filter_map(NodeOrToken::into_token) |
| .filter(|t| t.kind() == T![,]) |
| .take_while(|t| t.text_range().start() <= token.text_range().start()) |
| .count(); |
| |
| let mut res = SignatureHelp { |
| doc: None, |
| signature: String::new(), |
| parameters: vec![], |
| active_parameter: Some(active_parameter), |
| }; |
| |
| let fields; |
| |
| let db = sema.db; |
| let path_res = sema.resolve_path(path)?; |
| if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res { |
| fields = variant.fields(db); |
| let en = variant.parent_enum(db); |
| |
| res.doc = en.docs(db); |
| format_to!( |
| res.signature, |
| "enum {}::{} {{ ", |
| en.name(db).display(db), |
| variant.name(db).display(db) |
| ); |
| } else { |
| let adt = match path_res { |
| PathResolution::SelfType(imp) => imp.self_ty(db).as_adt()?, |
| PathResolution::Def(ModuleDef::Adt(adt)) => adt, |
| _ => return None, |
| }; |
| |
| match adt { |
| hir::Adt::Struct(it) => { |
| fields = it.fields(db); |
| res.doc = it.docs(db); |
| format_to!(res.signature, "struct {} {{ ", it.name(db).display(db)); |
| } |
| hir::Adt::Union(it) => { |
| fields = it.fields(db); |
| res.doc = it.docs(db); |
| format_to!(res.signature, "union {} {{ ", it.name(db).display(db)); |
| } |
| _ => return None, |
| } |
| } |
| |
| let mut fields = |
| fields.into_iter().map(|field| (field.name(db), Some(field))).collect::<FxIndexMap<_, _>>(); |
| let mut buf = String::new(); |
| for (field, ty) in fields2 { |
| let name = field.name(db); |
| format_to!(buf, "{}: {}", name.display(db), ty.display_truncated(db, Some(20))); |
| res.push_record_field(&buf); |
| buf.clear(); |
| |
| if let Some(field) = fields.get_mut(&name) { |
| *field = None; |
| } |
| } |
| for (name, field) in fields { |
| let Some(field) = field else { continue }; |
| format_to!(buf, "{}: {}", name.display(db), field.ty(db).display_truncated(db, Some(20))); |
| res.push_record_field(&buf); |
| buf.clear(); |
| } |
| res.signature.push_str(" }"); |
| Some(res) |
| } |
| |
| fn signature_help_for_tuple_pat_ish( |
| db: &RootDatabase, |
| mut res: SignatureHelp, |
| pat: &SyntaxNode, |
| token: SyntaxToken, |
| mut field_pats: AstChildren<ast::Pat>, |
| fields: impl ExactSizeIterator<Item = hir::Type>, |
| ) -> SignatureHelp { |
| let rest_pat = field_pats.find(|it| matches!(it, ast::Pat::RestPat(_))); |
| let is_left_of_rest_pat = |
| rest_pat.map_or(true, |it| token.text_range().start() < it.syntax().text_range().end()); |
| |
| let commas = pat |
| .children_with_tokens() |
| .filter_map(NodeOrToken::into_token) |
| .filter(|t| t.kind() == T![,]); |
| |
| res.active_parameter = { |
| Some(if is_left_of_rest_pat { |
| commas.take_while(|t| t.text_range().start() <= token.text_range().start()).count() |
| } else { |
| let n_commas = commas |
| .collect::<Vec<_>>() |
| .into_iter() |
| .rev() |
| .take_while(|t| t.text_range().start() > token.text_range().start()) |
| .count(); |
| fields.len().saturating_sub(1).saturating_sub(n_commas) |
| }) |
| }; |
| |
| let mut buf = String::new(); |
| for ty in fields { |
| format_to!(buf, "{}", ty.display_truncated(db, Some(20))); |
| res.push_call_param(&buf); |
| buf.clear(); |
| } |
| res.signature.push(')'); |
| res |
| } |
| #[cfg(test)] |
| mod tests { |
| use std::iter; |
| |
| use expect_test::{expect, Expect}; |
| use ide_db::base_db::FilePosition; |
| use stdx::format_to; |
| use test_fixture::ChangeFixture; |
| |
| use crate::RootDatabase; |
| |
| /// Creates analysis from a multi-file fixture, returns positions marked with $0. |
| pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) { |
| let change_fixture = ChangeFixture::parse(ra_fixture); |
| let mut database = RootDatabase::default(); |
| database.apply_change(change_fixture.change); |
| let (file_id, range_or_offset) = |
| change_fixture.file_position.expect("expected a marker ($0)"); |
| let offset = range_or_offset.expect_offset(); |
| (database, FilePosition { file_id, offset }) |
| } |
| |
| #[track_caller] |
| fn check(ra_fixture: &str, expect: Expect) { |
| let fixture = format!( |
| r#" |
| //- minicore: sized, fn |
| {ra_fixture} |
| "# |
| ); |
| let (db, position) = position(&fixture); |
| let sig_help = crate::signature_help::signature_help(&db, position); |
| let actual = match sig_help { |
| Some(sig_help) => { |
| let mut rendered = String::new(); |
| if let Some(docs) = &sig_help.doc { |
| format_to!(rendered, "{}\n------\n", docs.as_str()); |
| } |
| format_to!(rendered, "{}\n", sig_help.signature); |
| let mut offset = 0; |
| for (i, range) in sig_help.parameter_ranges().iter().enumerate() { |
| let is_active = sig_help.active_parameter == Some(i); |
| |
| let start = u32::from(range.start()); |
| let gap = start.checked_sub(offset).unwrap_or_else(|| { |
| panic!("parameter ranges out of order: {:?}", sig_help.parameter_ranges()) |
| }); |
| rendered.extend(iter::repeat(' ').take(gap as usize)); |
| let param_text = &sig_help.signature[*range]; |
| let width = param_text.chars().count(); // … |
| let marker = if is_active { '^' } else { '-' }; |
| rendered.extend(iter::repeat(marker).take(width)); |
| offset += gap + u32::from(range.len()); |
| } |
| if !sig_help.parameter_ranges().is_empty() { |
| format_to!(rendered, "\n"); |
| } |
| rendered |
| } |
| None => String::new(), |
| }; |
| expect.assert_eq(&actual); |
| } |
| |
| #[test] |
| fn test_fn_signature_two_args() { |
| check( |
| r#" |
| fn foo(x: u32, y: u32) -> u32 {x + y} |
| fn bar() { foo($03, ); } |
| "#, |
| expect![[r#" |
| fn foo(x: u32, y: u32) -> u32 |
| ^^^^^^ ------ |
| "#]], |
| ); |
| check( |
| r#" |
| fn foo(x: u32, y: u32) -> u32 {x + y} |
| fn bar() { foo(3$0, ); } |
| "#, |
| expect![[r#" |
| fn foo(x: u32, y: u32) -> u32 |
| ^^^^^^ ------ |
| "#]], |
| ); |
| check( |
| r#" |
| fn foo(x: u32, y: u32) -> u32 {x + y} |
| fn bar() { foo(3,$0 ); } |
| "#, |
| expect![[r#" |
| fn foo(x: u32, y: u32) -> u32 |
| ------ ^^^^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| fn foo(x: u32, y: u32) -> u32 {x + y} |
| fn bar() { foo(3, $0); } |
| "#, |
| expect![[r#" |
| fn foo(x: u32, y: u32) -> u32 |
| ------ ^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_two_args_empty() { |
| check( |
| r#" |
| fn foo(x: u32, y: u32) -> u32 {x + y} |
| fn bar() { foo($0); } |
| "#, |
| expect![[r#" |
| fn foo(x: u32, y: u32) -> u32 |
| ^^^^^^ ------ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_two_args_first_generics() { |
| check( |
| r#" |
| fn foo<T, U: Copy + Display>(x: T, y: U) -> u32 |
| where T: Copy + Display, U: Debug |
| { x + y } |
| |
| fn bar() { foo($03, ); } |
| "#, |
| expect![[r#" |
| fn foo(x: i32, y: U) -> u32 |
| ^^^^^^ ---- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_no_params() { |
| check( |
| r#" |
| fn foo<T>() -> T where T: Copy + Display {} |
| fn bar() { foo($0); } |
| "#, |
| expect![[r#" |
| fn foo() -> T |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_for_impl() { |
| check( |
| r#" |
| struct F; |
| impl F { pub fn new() { } } |
| fn bar() { |
| let _ : F = F::new($0); |
| } |
| "#, |
| expect![[r#" |
| fn new() |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_for_method_self() { |
| check( |
| r#" |
| struct S; |
| impl S { pub fn do_it(&self) {} } |
| |
| fn bar() { |
| let s: S = S; |
| s.do_it($0); |
| } |
| "#, |
| expect![[r#" |
| fn do_it(&self) |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_for_method_with_arg() { |
| check( |
| r#" |
| struct S; |
| impl S { |
| fn foo(&self, x: i32) {} |
| } |
| |
| fn main() { S.foo($0); } |
| "#, |
| expect![[r#" |
| fn foo(&self, x: i32) |
| ^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_for_generic_method() { |
| check( |
| r#" |
| struct S<T>(T); |
| impl<T> S<T> { |
| fn foo(&self, x: T) {} |
| } |
| |
| fn main() { S(1u32).foo($0); } |
| "#, |
| expect![[r#" |
| fn foo(&self, x: u32) |
| ^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_for_method_with_arg_as_assoc_fn() { |
| check( |
| r#" |
| struct S; |
| impl S { |
| fn foo(&self, x: i32) {} |
| } |
| |
| fn main() { S::foo($0); } |
| "#, |
| expect![[r#" |
| fn foo(self: &S, x: i32) |
| ^^^^^^^^ ------ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_with_docs_simple() { |
| check( |
| r#" |
| /// test |
| // non-doc-comment |
| fn foo(j: u32) -> u32 { |
| j |
| } |
| |
| fn bar() { |
| let _ = foo($0); |
| } |
| "#, |
| expect![[r#" |
| test |
| ------ |
| fn foo(j: u32) -> u32 |
| ^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_with_docs() { |
| check( |
| r#" |
| /// Adds one to the number given. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// let five = 5; |
| /// |
| /// assert_eq!(6, my_crate::add_one(5)); |
| /// ``` |
| pub fn add_one(x: i32) -> i32 { |
| x + 1 |
| } |
| |
| pub fn r#do() { |
| add_one($0 |
| }"#, |
| expect![[r##" |
| Adds one to the number given. |
| |
| # Examples |
| |
| ``` |
| let five = 5; |
| |
| assert_eq!(6, my_crate::add_one(5)); |
| ``` |
| ------ |
| fn add_one(x: i32) -> i32 |
| ^^^^^^ |
| "##]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_with_docs_impl() { |
| check( |
| r#" |
| struct addr; |
| impl addr { |
| /// Adds one to the number given. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// let five = 5; |
| /// |
| /// assert_eq!(6, my_crate::add_one(5)); |
| /// ``` |
| pub fn add_one(x: i32) -> i32 { |
| x + 1 |
| } |
| } |
| |
| pub fn do_it() { |
| addr {}; |
| addr::add_one($0); |
| } |
| "#, |
| expect![[r##" |
| Adds one to the number given. |
| |
| # Examples |
| |
| ``` |
| let five = 5; |
| |
| assert_eq!(6, my_crate::add_one(5)); |
| ``` |
| ------ |
| fn add_one(x: i32) -> i32 |
| ^^^^^^ |
| "##]], |
| ); |
| } |
| |
| #[test] |
| fn test_fn_signature_with_docs_from_actix() { |
| check( |
| r#" |
| trait Actor { |
| /// Actor execution context type |
| type Context; |
| } |
| trait WriteHandler<E> |
| where |
| Self: Actor |
| { |
| /// Method is called when writer finishes. |
| /// |
| /// By default this method stops actor's `Context`. |
| fn finished(&mut self, ctx: &mut Self::Context) {} |
| } |
| |
| fn foo(mut r: impl WriteHandler<()>) { |
| r.finished($0); |
| } |
| "#, |
| expect![[r#" |
| Method is called when writer finishes. |
| |
| By default this method stops actor's `Context`. |
| ------ |
| fn finished(&mut self, ctx: &mut <impl WriteHandler<()> as Actor>::Context) |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn call_info_bad_offset() { |
| check( |
| r#" |
| fn foo(x: u32, y: u32) -> u32 {x + y} |
| fn bar() { foo $0 (3, ); } |
| "#, |
| expect![[""]], |
| ); |
| } |
| |
| #[test] |
| fn outside_of_arg_list() { |
| check( |
| r#" |
| fn foo(a: u8) {} |
| fn f() { |
| foo(123)$0 |
| } |
| "#, |
| expect![[]], |
| ); |
| check( |
| r#" |
| fn foo<T>(a: u8) {} |
| fn f() { |
| foo::<u32>$0() |
| } |
| "#, |
| expect![[]], |
| ); |
| check( |
| r#" |
| fn foo(a: u8) -> u8 {a} |
| fn bar(a: u8) -> u8 {a} |
| fn f() { |
| foo(bar(123)$0) |
| } |
| "#, |
| expect![[r#" |
| fn foo(a: u8) -> u8 |
| ^^^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| struct Vec<T>(T); |
| struct Vec2<T>(T); |
| fn f() { |
| let _: Vec2<Vec<u8>$0> |
| } |
| "#, |
| expect![[r#" |
| struct Vec2<T> |
| ^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_nested_method_in_lambda() { |
| check( |
| r#" |
| struct Foo; |
| impl Foo { fn bar(&self, _: u32) { } } |
| |
| fn bar(_: u32) { } |
| |
| fn main() { |
| let foo = Foo; |
| std::thread::spawn(move || foo.bar($0)); |
| } |
| "#, |
| expect![[r#" |
| fn bar(&self, _: u32) |
| ^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn works_for_tuple_structs() { |
| check( |
| r#" |
| /// A cool tuple struct |
| struct S(u32, i32); |
| fn main() { |
| let s = S(0, $0); |
| } |
| "#, |
| expect![[r#" |
| A cool tuple struct |
| ------ |
| struct S(u32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn tuple_struct_pat() { |
| check( |
| r#" |
| /// A cool tuple struct |
| struct S(u32, i32); |
| fn main() { |
| let S(0, $0); |
| } |
| "#, |
| expect![[r#" |
| A cool tuple struct |
| ------ |
| struct S (u32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn tuple_struct_pat_rest() { |
| check( |
| r#" |
| /// A cool tuple struct |
| struct S(u32, i32, f32, u16); |
| fn main() { |
| let S(0, .., $0); |
| } |
| "#, |
| expect![[r#" |
| A cool tuple struct |
| ------ |
| struct S (u32, i32, f32, u16) |
| --- --- --- ^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| /// A cool tuple struct |
| struct S(u32, i32, f32, u16, u8); |
| fn main() { |
| let S(0, .., $0, 0); |
| } |
| "#, |
| expect![[r#" |
| A cool tuple struct |
| ------ |
| struct S (u32, i32, f32, u16, u8) |
| --- --- --- ^^^ -- |
| "#]], |
| ); |
| check( |
| r#" |
| /// A cool tuple struct |
| struct S(u32, i32, f32, u16); |
| fn main() { |
| let S($0, .., 1); |
| } |
| "#, |
| expect![[r#" |
| A cool tuple struct |
| ------ |
| struct S (u32, i32, f32, u16) |
| ^^^ --- --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| /// A cool tuple struct |
| struct S(u32, i32, f32, u16, u8); |
| fn main() { |
| let S(1, .., 1, $0, 2); |
| } |
| "#, |
| expect![[r#" |
| A cool tuple struct |
| ------ |
| struct S (u32, i32, f32, u16, u8) |
| --- --- --- ^^^ -- |
| "#]], |
| ); |
| check( |
| r#" |
| /// A cool tuple struct |
| struct S(u32, i32, f32, u16); |
| fn main() { |
| let S(1, $0.., 1); |
| } |
| "#, |
| expect![[r#" |
| A cool tuple struct |
| ------ |
| struct S (u32, i32, f32, u16) |
| --- ^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| /// A cool tuple struct |
| struct S(u32, i32, f32, u16); |
| fn main() { |
| let S(1, ..$0, 1); |
| } |
| "#, |
| expect![[r#" |
| A cool tuple struct |
| ------ |
| struct S (u32, i32, f32, u16) |
| --- ^^^ --- --- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn generic_struct() { |
| check( |
| r#" |
| struct S<T>(T); |
| fn main() { |
| let s = S($0); |
| } |
| "#, |
| expect![[r#" |
| struct S({unknown}) |
| ^^^^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn works_for_enum_variants() { |
| check( |
| r#" |
| enum E { |
| /// A Variant |
| A(i32), |
| /// Another |
| B, |
| /// And C |
| C { a: i32, b: i32 } |
| } |
| |
| fn main() { |
| let a = E::A($0); |
| } |
| "#, |
| expect![[r#" |
| A Variant |
| ------ |
| enum E::A(i32) |
| ^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn cant_call_struct_record() { |
| check( |
| r#" |
| struct S { x: u32, y: i32 } |
| fn main() { |
| let s = S($0); |
| } |
| "#, |
| expect![[""]], |
| ); |
| } |
| |
| #[test] |
| fn cant_call_enum_record() { |
| check( |
| r#" |
| enum E { |
| /// A Variant |
| A(i32), |
| /// Another |
| B, |
| /// And C |
| C { a: i32, b: i32 } |
| } |
| |
| fn main() { |
| let a = E::C($0); |
| } |
| "#, |
| expect![[""]], |
| ); |
| } |
| |
| #[test] |
| fn fn_signature_for_call_in_macro() { |
| check( |
| r#" |
| macro_rules! id { ($($tt:tt)*) => { $($tt)* } } |
| fn foo() { } |
| id! { |
| fn bar() { foo($0); } |
| } |
| "#, |
| expect![[r#" |
| fn foo() |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn fn_signature_for_method_call_defined_in_macro() { |
| check( |
| r#" |
| macro_rules! id { ($($tt:tt)*) => { $($tt)* } } |
| struct S; |
| id! { |
| impl S { |
| fn foo<'a>(&'a mut self) {} |
| } |
| } |
| fn test() { S.foo($0); } |
| "#, |
| expect![[r#" |
| fn foo(&'a mut self) |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn call_info_for_lambdas() { |
| check( |
| r#" |
| struct S; |
| fn foo(s: S) -> i32 { 92 } |
| fn main() { |
| (|s| foo(s))($0) |
| } |
| "#, |
| expect![[r#" |
| (s: S) -> i32 |
| ^^^^ |
| "#]], |
| ) |
| } |
| |
| #[test] |
| fn call_info_for_fn_def_over_reference() { |
| check( |
| r#" |
| struct S; |
| fn foo(s: S) -> i32 { 92 } |
| fn main() { |
| let bar = &&&&&foo; |
| bar($0); |
| } |
| "#, |
| expect![[r#" |
| fn foo(s: S) -> i32 |
| ^^^^ |
| "#]], |
| ) |
| } |
| |
| #[test] |
| fn call_info_for_fn_ptr() { |
| check( |
| r#" |
| fn main(f: fn(i32, f64) -> char) { |
| f(0, $0) |
| } |
| "#, |
| expect![[r#" |
| (i32, f64) -> char |
| --- ^^^ |
| "#]], |
| ) |
| } |
| |
| #[test] |
| fn call_info_for_unclosed_call() { |
| check( |
| r#" |
| fn foo(foo: u32, bar: u32) {} |
| fn main() { |
| foo($0 |
| }"#, |
| expect![[r#" |
| fn foo(foo: u32, bar: u32) |
| ^^^^^^^^ -------- |
| "#]], |
| ); |
| // check with surrounding space |
| check( |
| r#" |
| fn foo(foo: u32, bar: u32) {} |
| fn main() { |
| foo( $0 |
| }"#, |
| expect![[r#" |
| fn foo(foo: u32, bar: u32) |
| ^^^^^^^^ -------- |
| "#]], |
| ) |
| } |
| |
| #[test] |
| fn test_multiline_argument() { |
| check( |
| r#" |
| fn callee(a: u8, b: u8) {} |
| fn main() { |
| callee(match 0 { |
| 0 => 1,$0 |
| }) |
| }"#, |
| expect![[r#""#]], |
| ); |
| check( |
| r#" |
| fn callee(a: u8, b: u8) {} |
| fn main() { |
| callee(match 0 { |
| 0 => 1, |
| },$0) |
| }"#, |
| expect![[r#" |
| fn callee(a: u8, b: u8) |
| ----- ^^^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| fn callee(a: u8, b: u8) {} |
| fn main() { |
| callee($0match 0 { |
| 0 => 1, |
| }) |
| }"#, |
| expect![[r#" |
| fn callee(a: u8, b: u8) |
| ^^^^^ ----- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_generics_simple() { |
| check( |
| r#" |
| /// Option docs. |
| enum Option<T> { |
| Some(T), |
| None, |
| } |
| |
| fn f() { |
| let opt: Option<$0 |
| } |
| "#, |
| expect![[r#" |
| Option docs. |
| ------ |
| enum Option<T> |
| ^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_generics_on_variant() { |
| check( |
| r#" |
| /// Option docs. |
| enum Option<T> { |
| /// Some docs. |
| Some(T), |
| /// None docs. |
| None, |
| } |
| |
| use Option::*; |
| |
| fn f() { |
| None::<$0 |
| } |
| "#, |
| expect![[r#" |
| None docs. |
| ------ |
| enum Option<T> |
| ^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_lots_of_generics() { |
| check( |
| r#" |
| trait Tr<T> {} |
| |
| struct S<T>(T); |
| |
| impl<T> S<T> { |
| fn f<G, H>(g: G, h: impl Tr<G>) where G: Tr<()> {} |
| } |
| |
| fn f() { |
| S::<u8>::f::<(), $0 |
| } |
| "#, |
| expect![[r#" |
| fn f<G: Tr<()>, H> |
| --------- ^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_generics_in_trait_ufcs() { |
| check( |
| r#" |
| trait Tr { |
| fn f<T: Tr, U>() {} |
| } |
| |
| struct S; |
| |
| impl Tr for S {} |
| |
| fn f() { |
| <S as Tr>::f::<$0 |
| } |
| "#, |
| expect![[r#" |
| fn f<T: Tr, U> |
| ^^^^^ - |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_generics_in_method_call() { |
| check( |
| r#" |
| struct S; |
| |
| impl S { |
| fn f<T>(&self) {} |
| } |
| |
| fn f() { |
| S.f::<$0 |
| } |
| "#, |
| expect![[r#" |
| fn f<T> |
| ^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_generic_param_in_method_call() { |
| check( |
| r#" |
| struct Foo; |
| impl Foo { |
| fn test<V>(&mut self, val: V) {} |
| } |
| fn sup() { |
| Foo.test($0) |
| } |
| "#, |
| expect![[r#" |
| fn test(&mut self, val: V) |
| ^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_generic_kinds() { |
| check( |
| r#" |
| fn callee<'a, const A: u8, T, const C: u8>() {} |
| |
| fn f() { |
| callee::<'static, $0 |
| } |
| "#, |
| expect![[r#" |
| fn callee<'a, const A: u8, T, const C: u8> |
| -- ^^^^^^^^^^^ - ----------- |
| "#]], |
| ); |
| check( |
| r#" |
| fn callee<'a, const A: u8, T, const C: u8>() {} |
| |
| fn f() { |
| callee::<NON_LIFETIME$0 |
| } |
| "#, |
| expect![[r#" |
| fn callee<'a, const A: u8, T, const C: u8> |
| -- ^^^^^^^^^^^ - ----------- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_trait_assoc_types() { |
| check( |
| r#" |
| trait Trait<'a, T> { |
| type Assoc; |
| } |
| fn f() -> impl Trait<(), $0 |
| "#, |
| expect![[r#" |
| trait Trait<'a, T, Assoc = …> |
| -- - ^^^^^^^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| trait Iterator { |
| type Item; |
| } |
| fn f() -> impl Iterator<$0 |
| "#, |
| expect![[r#" |
| trait Iterator<Item = …> |
| ^^^^^^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| trait Iterator { |
| type Item; |
| } |
| fn f() -> impl Iterator<Item = $0 |
| "#, |
| expect![[r#" |
| trait Iterator<Item = …> |
| ^^^^^^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| trait Tr { |
| type A; |
| type B; |
| } |
| fn f() -> impl Tr<$0 |
| "#, |
| expect![[r#" |
| trait Tr<A = …, B = …> |
| ^^^^^ ----- |
| "#]], |
| ); |
| check( |
| r#" |
| trait Tr { |
| type A; |
| type B; |
| } |
| fn f() -> impl Tr<B$0 |
| "#, |
| expect![[r#" |
| trait Tr<A = …, B = …> |
| ^^^^^ ----- |
| "#]], |
| ); |
| check( |
| r#" |
| trait Tr { |
| type A; |
| type B; |
| } |
| fn f() -> impl Tr<B = $0 |
| "#, |
| expect![[r#" |
| trait Tr<B = …, A = …> |
| ^^^^^ ----- |
| "#]], |
| ); |
| check( |
| r#" |
| trait Tr { |
| type A; |
| type B; |
| } |
| fn f() -> impl Tr<B = (), $0 |
| "#, |
| expect![[r#" |
| trait Tr<B = …, A = …> |
| ----- ^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_supertrait_assoc() { |
| check( |
| r#" |
| trait Super { |
| type SuperTy; |
| } |
| trait Sub: Super + Super { |
| type SubTy; |
| } |
| fn f() -> impl Sub<$0 |
| "#, |
| expect![[r#" |
| trait Sub<SubTy = …, SuperTy = …> |
| ^^^^^^^^^ ----------- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn no_assoc_types_outside_type_bounds() { |
| check( |
| r#" |
| trait Tr<T> { |
| type Assoc; |
| } |
| |
| impl Tr<$0 |
| "#, |
| expect![[r#" |
| trait Tr<T> |
| ^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn impl_trait() { |
| // FIXME: Substitute type vars in impl trait (`U` -> `i8`) |
| check( |
| r#" |
| trait Trait<T> {} |
| struct Wrap<T>(T); |
| fn foo<U>(x: Wrap<impl Trait<U>>) {} |
| fn f() { |
| foo::<i8>($0) |
| } |
| "#, |
| expect![[r#" |
| fn foo(x: Wrap<impl Trait<U>>) |
| ^^^^^^^^^^^^^^^^^^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn fully_qualified_syntax() { |
| check( |
| r#" |
| fn f() { |
| trait A { fn foo(&self, other: Self); } |
| A::foo(&self$0, other); |
| } |
| "#, |
| expect![[r#" |
| fn foo(self: &Self, other: Self) |
| ^^^^^^^^^^^ ----------- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn help_for_generic_call() { |
| check( |
| r#" |
| fn f<F: FnOnce(u8, u16) -> i32>(f: F) { |
| f($0) |
| } |
| "#, |
| expect![[r#" |
| (u8, u16) -> i32 |
| ^^ --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn f<T, F: FnOnce(&T, u16) -> &T>(f: F) { |
| f($0) |
| } |
| "#, |
| expect![[r#" |
| (&T, u16) -> &T |
| ^^ --- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn regression_13579() { |
| check( |
| r#" |
| fn f() { |
| take(2)($0); |
| } |
| |
| fn take<C, Error>( |
| count: C |
| ) -> impl Fn() -> C { |
| move || count |
| } |
| "#, |
| expect![[r#" |
| () -> i32 |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn record_literal() { |
| check( |
| r#" |
| struct Strukt<T, U = ()> { |
| t: T, |
| u: U, |
| unit: (), |
| } |
| fn f() { |
| Strukt { |
| u: 0, |
| $0 |
| } |
| } |
| "#, |
| expect![[r#" |
| struct Strukt { u: i32, t: T, unit: () } |
| ------ ^^^^ -------- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn record_literal_nonexistent_field() { |
| check( |
| r#" |
| struct Strukt { |
| a: u8, |
| } |
| fn f() { |
| Strukt { |
| b: 8, |
| $0 |
| } |
| } |
| "#, |
| expect![[r#" |
| struct Strukt { a: u8 } |
| ----- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn tuple_variant_record_literal() { |
| check( |
| r#" |
| enum Opt { |
| Some(u8), |
| } |
| fn f() { |
| Opt::Some {$0} |
| } |
| "#, |
| expect![[r#" |
| enum Opt::Some { 0: u8 } |
| ^^^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| enum Opt { |
| Some(u8), |
| } |
| fn f() { |
| Opt::Some {0:0,$0} |
| } |
| "#, |
| expect![[r#" |
| enum Opt::Some { 0: u8 } |
| ----- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn record_literal_self() { |
| check( |
| r#" |
| struct S { t: u8 } |
| impl S { |
| fn new() -> Self { |
| Self { $0 } |
| } |
| } |
| "#, |
| expect![[r#" |
| struct S { t: u8 } |
| ^^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn record_pat() { |
| check( |
| r#" |
| struct Strukt<T, U = ()> { |
| t: T, |
| u: U, |
| unit: (), |
| } |
| fn f() { |
| let Strukt { |
| u: 0, |
| $0 |
| } |
| } |
| "#, |
| expect![[r#" |
| struct Strukt { u: i32, t: T, unit: () } |
| ------ ^^^^ -------- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_enum_in_nested_method_in_lambda() { |
| check( |
| r#" |
| enum A { |
| A, |
| B |
| } |
| |
| fn bar(_: A) { } |
| |
| fn main() { |
| let foo = Foo; |
| std::thread::spawn(move || { bar(A:$0) } ); |
| } |
| "#, |
| expect![[r#" |
| fn bar(_: A) |
| ^^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_tuple_expr_free() { |
| check( |
| r#" |
| fn main() { |
| (0$0, 1, 3); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32) |
| ^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| ($0 1, 3); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| ^^^ --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| (1, 3 $0); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| (1, 3 $0,); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_tuple_expr_expected() { |
| check( |
| r#" |
| fn main() { |
| let _: (&str, u32, u32)= ($0, 1, 3); |
| } |
| "#, |
| expect![[r#" |
| (&str, u32, u32) |
| ^^^^ --- --- |
| "#]], |
| ); |
| // FIXME: Should typeck report a 4-ary tuple for the expression here? |
| check( |
| r#" |
| fn main() { |
| let _: (&str, u32, u32, u32) = ($0, 1, 3); |
| } |
| "#, |
| expect![[r#" |
| (&str, u32, u32) |
| ^^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let _: (&str, u32, u32)= ($0, 1, 3, 5); |
| } |
| "#, |
| expect![[r#" |
| (&str, u32, u32, i32) |
| ^^^^ --- --- --- |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_tuple_pat_free() { |
| check( |
| r#" |
| fn main() { |
| let ($0, 1, 3); |
| } |
| "#, |
| expect![[r#" |
| ({unknown}, i32, i32) |
| ^^^^^^^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (0$0, 1, 3); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32) |
| ^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let ($0 1, 3); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| ^^^ --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3 $0); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3 $0,); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3 $0, ..); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3, .., $0); |
| } |
| "#, |
| // FIXME: This is wrong, this should not mark the last as active |
| expect![[r#" |
| (i32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| } |
| |
| #[test] |
| fn test_tuple_pat_expected() { |
| check( |
| r#" |
| fn main() { |
| let (0$0, 1, 3): (i32, i32, i32); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32) |
| ^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let ($0, 1, 3): (i32, i32, i32); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32) |
| ^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3 $0): (i32,); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3 $0, ..): (i32, i32, i32, i32); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32, i32) |
| --- ^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3, .., $0): (i32, i32, i32); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32) |
| --- --- ^^^ |
| "#]], |
| ); |
| } |
| #[test] |
| fn test_tuple_pat_expected_inferred() { |
| check( |
| r#" |
| fn main() { |
| let (0$0, 1, 3) = (1, 2 ,3); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32) |
| ^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let ($0 1, 3) = (1, 2, 3); |
| } |
| "#, |
| // FIXME: Should typeck report a 3-ary tuple for the pattern here? |
| expect![[r#" |
| (i32, i32) |
| ^^^ --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3 $0) = (1,); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32) |
| --- ^^^ |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3 $0, ..) = (1, 2, 3, 4); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32, i32) |
| --- ^^^ --- --- |
| "#]], |
| ); |
| check( |
| r#" |
| fn main() { |
| let (1, 3, .., $0) = (1, 2, 3); |
| } |
| "#, |
| expect![[r#" |
| (i32, i32, i32) |
| --- --- ^^^ |
| "#]], |
| ); |
| } |
| } |