2260 lines
51 KiB
Rust
2260 lines
51 KiB
Rust
//! 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)
|
|
--- --- ^^^
|
|
"#]],
|
|
);
|
|
}
|
|
}
|